1 Preface

Some R stuff:

  • You can download the entire Rmd for this Markdown by clicking on the upper right Code button.
  • You can also use the upper right Code button to hide all code if you just want to focus on the analysis.

Given that we cannot access the data and I was under the impression that our coding walk-through today was somewhat helpful, I attempted to create a template for the analysis that you can use for the data. I was not able to access hospitalizations on the county level from the Covid19 DataHub so I will be using deaths inlieu of hospitalizations for this walk-through.

This template makes the following assumptions:

  • The COVID-19 DataHub provides the data at three units of aggregation: country, state, and county. From my understanding, you are modeling data for Greater St. Louis (i.e., St. Louis MSA). To get the data similar to what Peter has access to, I summed both cases and deaths for counties in both Illinois (‘Bond’, ‘Calhoun’, ‘Clinton’, ‘Jersey’, ‘Macoupin’, ‘Madison’, ‘Monroe’) and Missouri (‘Crawford’, ‘Franklin’, ‘Jefferson’, ‘Lincoln’, ‘St. Charles’, ‘St. Clair’, ‘St. Louis’, ‘St. Louis City’, ‘Warren’) based on the Greater St. Louis Wikipedia Page.

  • On top of my head, I did not remember the study period that you had for the analysis. So I have used data starting from Jan 04, 2021 (first Monday of the Year) for this analysis.

  • For the St. Louis Health Department, I assumed that the non_weekend holidays matched those of the New York Stock Exchange. These were extracted using HOLIDAY_SEQUENCE(start_date = '2021-01-04', end_date = '2022-08-01', calendar = 'NYSE') from the tidyquant package. The exact holiday dates from this function call are: 2021-01-18, 2021-02-15, 2021-04-02, 2021-05-31, 2021-07-05, 2021-09-06, 2021-11-25, 2021-12-24, 2022-01-17, 2022-02-21, 2022-04-15, 2022-05-30, 2022-07-04.

  • I kind of eyeballed the dates for the dominant COVID-19 variant based on the following NY Times Article. I have used the plot within the article to define the dominant variant to be as follows: dominant_variant = case_when(date < ymd('2021-03-01') ~ 'Epsilon', date < ymd('2021-06-15') ~ 'Alpha', date < ymd('2021-12-15') ~ 'Delta', date >= ymd('2021-12-15') ~ 'Omicron') %>% as_factor(). This allowed me to take into account the ‘entire’ time-series rather than just the Omicron part.

  • I assumed that one of the end goals from your analysis would be forecasting future hospitalizations. So far, in our meetings, we discussed explanatory modeling. So I divided my data into a training and a holdout sample – primarily for the purpose of showing you how its done. Obviously, you can override this by ignoring the holdout and just focusing on the training results (which you can also set to be the entire time period of interest).

  • I did not lag the confirmed cases. I believe you should test out different lags within the context of both the auto.arima() and the other models since we would be hypothesizing that the lagged confirmed cases would lead to future hospitalizations. It is my understanding that the auto.arima() does not account for the lagged relationships on the exogenous variables. See the Lagged Predictors in the fpp2 book. The implications of introducing the lagged predictors on the current code are as follows:

    • The lag can be computed as follows within the mutate function: confirmed1 = lag(confirmed, k = 1) for lag1. Changing k would change the lag.
    • The lag should also be accounted for in the initial_time_split() to ensure a seperation between the training and testing dataset. In that function, the default value for lag = 0, and you would need to change it to the highest lag you are evaluating.
  • I ignored potentially relevant predictors such as the stringency_index and people_fully_vaccinated. These are found in the covid_tbl but I did not aggregate them for the St. Louis MSA. So you can easily include them by adding them to both the select() used to create st_louis_agg_tbl in Section 3, and then summing over them with the summarise_at() used later in the code. Other potential predictors include: (a) an indicator variable indicating the emergence of a new variant (e.g., this variable can have a yes in the 2 weeks before and after we switch to a new dominant variable in our code); (b) leading the holiday variable (with lags up to 1-3 weeks), with the hypothesis that gatherings and travel during a holiday will have future effects on hospitalizations (see the lead() function which has the exact same syntax as the lag() function); and (c) lagging indicators of percent of positive tests, i.e., a spike in the percent of positive tests which may be more important than the confirmed cases with latter waves since more people are testing at home. These would be nice to explore as you continue to build the model.


2 Questions for the Group Based on My Preliminary Analysis Below

  • How should we handle negative counts in cases/deaths/hospitalizations? See the death count on March 29, 2021 in the plot in Section 5.1 as an example.

  • Should we worry about the residuals’ departure from normality? See the results of the Shapiro-Wilk Test in Section 7.2.2.

  • Do you agree with my comment about lagged predictors? See the second to last assumption in the Preface section.

  • How much weight should we put on the fact that the rsq of all models are small? See the last column (scrollable) in the tabulated training and testing results in Sections 7 and 8.


3 Required Packages

The code chunk below contains the packages used in this analysis

# tidyverse pkg for data manipulation
if(!require(tidyverse)) {install.packages('tidyverse'); library(tidyverse)}

# readr for reading files
if(!require(readr)) {install.packages('readr'); library(readr)}

# forecast package for some quick displays and analyses
if(!require(forecast)) {install.packages('forecast'); library(forecast)}

# timetk for tidy time-series preprocessing
if(!require(timetk)) {install.packages('timetk'); library(timetk)}

# tidyquant for tidy time-series analysis
if(!require(tidyquant)) {install.packages('tidyquant'); library(tidyquant)}

# modeltime for tidy forecasting
if(!require(modeltime)) {install.packages('modeltime'); library(modeltime)}

# for interactive plots
if(!require(plotly)) {install.packages('plotly'); library(plotly)}

# for tidy machine learning
if(!require(tidymodels)) {install.packages('tidymodels'); library(tidymodels)}

# for the xgboost model
if(!require(xgboost)) {install.packages('xgboost'); library(xgboost)}

4 Extracting COVID-19 Data

Per the Preface, I read the data used in this analysis from the COVID-19 DataHub. To not require the installing of the COVID19 package, I have downloaded the zip file and unzipped using R. Then, I filtered the data to Missouri and Illinois, selected the appropriate counties, and computed the variables that we are interested in.

# creating a temp file for downloading the data
temp = tempfile() 

# download the file to temporary location
download.file("https://storage.covid19datahub.io/country/USA.csv.zip", temp)

# unzip and read the file
covid_tbl = unz(temp, "USA.csv") %>% 
  # reading the data from the CSV
  read_csv() %>% 
  # filtering to Missouri and Illinois
  filter(administrative_area_level_2 %in% c('Illinois', 'Missouri') )


st_louis_tbl = covid_tbl %>% 
  # Relevant Counties in Illnois
  filter( 
    (administrative_area_level_2 == 'Illinois' &
       administrative_area_level_3 %in% c('Bond', 'Calhoun', 'Clinton', 'Jersey', 'Macoupin', 'Madison', 'Monroe') ) | 
      # Relevant Counties in Missouri 
      (administrative_area_level_2 == 'Missouri' &
         administrative_area_level_3 %in% c('Crawford', 'Franklin', 'Jefferson', 'Lincoln', 'St. Charles', 'St. Clair', 'St. Louis', 'St. Louis City', 'Warren'))
  )

# Aggregating the counts by day (so that we have an approximation of total numbers for st. louis)
st_louis_agg_tbl = 
  st_louis_tbl %>% 
  # grouping by date so we can created an aggregated summation across all counties
  group_by(date) %>% 
  select(date, 
         # variables to be summed across all counties in St. Louis
         confirmed, deaths, recovered, hosp, icu, vent, 
         # population will be summed only to see if the aggregation is reasonable
         # i.e., is the total pop close to the actual for the St. Louis MSA
         population,
         # other variables that can be included in the analysis later
         stringency_index) %>% 
  summarise_at(
    vars(confirmed, deaths, recovered, hosp, icu, vent, population),
    .funs = sum, na.rm = T
    ) %>% 
  ungroup() 

# remove temp file
unlink(temp) 

# glimpse of the data
glimpse(st_louis_agg_tbl)
## Rows: 878
## Columns: 8
## $ date       <date> 2020-03-07, 2020-03-08, 2020-03-09, 2020-03-10, 2020-03-11…
## $ confirmed  <dbl> 1, 1, 1, 1, 1, 1, 2, 2, 3, 5, 7, 12, 20, 26, 40, 77, 94, 99…
## $ deaths     <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 3, 3,…
## $ recovered  <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
## $ hosp       <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
## $ icu        <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
## $ vent       <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
## $ population <dbl> 994205, 994205, 994205, 994205, 994205, 994205, 994205, 994…

From the printout of the data, we have 8 variables and 878 days of data. Note that the dominant_variant was eyeballed as mentioned previously and the holidays variable assumes that your health department matches the holidays taken by the NYSE.


5 Plotting the Data for Saint Louis

5.1 Raw Data from the Repo

From the plot below, it is clear that this public dataset does not contain information on hospitalizations, vent, and ICU. Thus, I will model deaths as a function of cases. Peter, you can change the variables based on your dataset.

It is also clear that the data is cumulative so we will difference to have the daily new cases and deaths.

st_louis_agg_tbl %>% 
  pivot_longer(cols = c(confirmed, deaths, recovered, hosp, icu, vent, population),
               names_to = 'statistic') %>% 
  ggplot(
    aes(x = date, y = value)
  ) + 
  geom_line() +
  facet_wrap(~ statistic, scales = 'free_y', ncol = 1) + 
  theme_bw() +
  scale_x_date(breaks = scales::pretty_breaks(n = 12)) +
  scale_y_continuous(labels = scales::comma) +
  labs(title = 'Plots of the Time-Series of Potential Variables for St. Louis MSA',
       subtitle = 'Hosp, ICU, Recovered and Vent have no data',
       caption = 'Based on data aggregated from the COVID19 DataHub',
       x = 'Date',
       y = NULL)-> p1

ggplotly(p1)

5.2 Differenced Data Starting from Jan 04, 2021

Based on our explanatory analysis of the data, there seemed to be some large outliers in the beginning of 2021. So I am starting the analysis from the first Monday of the year. I differenced to compute the variables of interest. The code below plots the two time-series of interest (confirmed cases and confirmed deaths), color coded based on the COVID-19 dominant variant.

st_louis_agg_tbl = 
  st_louis_agg_tbl %>% 
  # keeping only relevant columns for forecasting
  select(date, confirmed, deaths) %>% 
  # converting the cumulative counts to new
  mutate(
    confirmed = confirmed - lag(confirmed),
    deaths = deaths - lag(deaths) 
  ) %>% 
  filter(date >= ymd('2021-01-04')) %>% 
  mutate(
    # creating a data frame of variants based on 
    # https://www.nytimes.com/interactive/2021/health/coronavirus-variant-tracker.html
    dominant_variant = 
      case_when(
        date < ymd('2021-03-01') ~ 'Epsilon',
        date < ymd('2021-06-15') ~ 'Alpha',
        date < ymd('2021-12-15') ~ 'Delta',
        date >= ymd('2021-12-15') ~ 'Omicron'
      ) %>% as_factor(),
    # creating a list of special holidays
         holidays = 
      if_else(date %in% HOLIDAY_SEQUENCE(start_date = min(date),
                                     end_date = max(date),
                                     calendar = 'NYSE'),
              true = 'yes',
              false = 'no') %>% as_factor()
           ) %>% 
  drop_na()

st_louis_agg_tbl %>% 
  pivot_longer(cols = c(confirmed, deaths),
               names_to = 'statistic') %>% 
  ggplot(
    aes(x = date, y = value, color = dominant_variant)
  ) + 
  geom_line() +
  facet_wrap(~ statistic, scales = 'free_y', ncol = 1) + 
  theme_bw() +
  scale_x_date(breaks = scales::pretty_breaks(n = 12)) + 
  scale_y_continuous(labels = scales::comma) +
  scale_color_brewer(palette = 'Paired') +
  labs(x = 'Date',
       y = 'New Daily Counts', 
       title = 'Plots of Confirmed Cases and Deaths for St. Louis MSA') -> p2

ggplotly(p2)

An important question to address (if this is in Peter’s data) is how do we handle negative counts. A naive fix in R would be to use the max of the value and 0 (see the pmax()). However, this ignores the misreporting of the data.

5.3 Examining the ACF, PACF and CCF

5.3.1 ACF of New Deaths

# extract the starting week number and week-day (i.e., 1 -7 where 7 is Sunday) number
starting_day = min(st_louis_agg_tbl$date) %>% wday()
starting_week = min(st_louis_agg_tbl$date) %>% week()

# creating a timeseries for the deaths (our response of interest)
deaths_ts = st_louis_agg_tbl %>% 
  pull(deaths) %>% 
  ts( 
    start = c(starting_week, starting_day),
    frequency = 7
  )

# plotting the acf as a ggplot object while dropping the useless lag 0
acf(deaths_ts, lag.max = 42, plot = F) %>% 
  autoplot() +
  scale_x_continuous(breaks = scales::pretty_breaks(n=7) ) +
  theme_bw() +
  labs(x = 'Lags in Weeks',
       title = 'ACF of New Deaths for the Greater St. Louis MSA',
       subtitle = 'The ACF shows a "weekly" pattern, i.e., we will need a seasonal ARIMA model')

5.3.2 PACF of New Deaths

# plotting the acf as a ggplot object while dropping the useless lag 0
pacf(deaths_ts, lag.max = 42, plot = F) %>% 
  autoplot() +
  scale_x_continuous(breaks = scales::pretty_breaks(n=7) ) +
  theme_bw() +
  labs(x = 'Lags in Weeks',
       title = 'PACF of New Deaths for the Greater St. Louis MSA',
       subtitle = 'The PACF shows a "weekly" pattern, i.e., we will need a seasonal ARIMA model')

5.3.3 CCF of New Deaths and Confirmed

The CCF plot below shows a significant cross correlation between both deaths and confirmed cases for up to 6 weeks of data. Note that the relationship seems to be significant primarily in the negative direction, which makes sense to me since lagged cases are predictors of deaths but the other way around is not true past two weeks. The data below also shows ‘weekly spikes’.

# creating a timeseries for the confirmed cases (similar to above)
confirmed_ts = st_louis_agg_tbl %>% 
  pull(confirmed) %>% 
  ts( 
    start = c(starting_week, starting_day),
    frequency = 7
  )

# plotting the CCF as a ggplot object
ccf(x = confirmed_ts, y = deaths_ts, plot = F, lag.max = 42) %>% 
  # converting the ccf plot to a ggplot object
  autoplot() +
  scale_x_continuous(breaks = scales::pretty_breaks(n=10)) + 
  labs(x = 'Lag in Weeks', y = 'CCF', 
       title = 'Cross Correlation Function of Deaths vs. Confirmed Cases',
       subtitle = 'Cases seem to be predictors of deaths (more than the other way around)') +
  theme_bw()


6 Creating time splits for Training and Validation

Below, I have capitalized on the initial_time_split() to split the time-series such that the first 90% of the data are used for model building and the remaining 10% are used for validation. We can redo this if we solely want to focus on the explanatory modeling component.

splits = initial_time_split(st_louis_agg_tbl, prop = 0.9)

# Printing out the splits so that we know the number of observations used for model training
splits
## <Training/Testing/Total>
## <517/58/575>
# Training data start and end dates
paste('The starting and ending dates for training are',
      splits$data[splits$in_id, 'date'] %>% head(n=1) %>% pull(),
      'and', 
      splits$data[splits$in_id, 'date'] %>% tail(n=1) %>% pull(),
      'respectively. For the holdout data, the starting and trainig dates are',
      splits$data[splits$out_id, 'date'] %>% head(n=1) %>% pull(),
      'and', 
      splits$data[splits$out_id, 'date'] %>% tail(n=1) %>% pull())
## [1] "The starting and ending dates for training are 2021-01-04 and 2022-06-04 respectively. For the holdout data, the starting and trainig dates are 2022-06-05 and 2022-08-01"

7 Training Different Time-Series Models

In this section, I have quickly trained the following five models:

  • A univariate Auto ARIMA model with no xreg
  • An Auto ARIMA model with confirmed, holidays (NYSE holidays) and dominant variant as our xreg
  • An Auto Arima Model with xgboost (using the xregs) on the Arima errors
  • The Prophet Model, originally developed by Facebook. See the Forecasting at Scale Paper for more details.
  • A linear regression model with predictors as.numeric(date) + confirmed + holidays + dominant_variant

I have capitalized on the modeltime package vignette to quickly run these models. Note that the modeltime API also allows for running other machine learning models for time-series applications.

7.1 Model Building

# a basic univariate ARIMA model using “Auto Arima” using arima_reg()
# using the modeltime pkg this will automatically pick the weekly seasonality
model_fit_arima =arima_reg() %>%
    set_engine(engine = "auto_arima") %>%
    fit(deaths ~ date, data = training(splits) )
## frequency = 7 observations per 1 week
# ARIMA with xreg
model_fit_arima_xreg = arima_reg() %>%
  set_engine(engine = "auto_arima") %>%
  # confirmed, holidays and dominant variant as our xreg
  fit(
    deaths ~ date + confirmed + holidays + dominant_variant,
      data = training(splits) 
    )
## frequency = 7 observations per 1 week
# Auto Arima Model with xgboost on the Arima errors
model_fit_arima_boosted = arima_boost(
    min_n = 2,
    learn_rate = 0.015
) %>%
    set_engine(engine = "auto_arima_xgboost") %>%
    fit(deaths ~ date + confirmed + holidays + dominant_variant,
        data = training(splits) )
## frequency = 7 observations per 1 week
# the prophet model used by Facebook
model_fit_prophet = prophet_reg() %>%
  set_engine(engine = "prophet") %>%
  fit(deaths ~ date + confirmed + holidays + dominant_variant,
      data = training(splits) )
## Disabling yearly seasonality. Run prophet with yearly.seasonality=TRUE to override this.
## Disabling daily seasonality. Run prophet with daily.seasonality=TRUE to override this.
# Linear Regression 
model_fit_lm = linear_reg() %>%
    set_engine("lm") %>%
    fit(deaths ~ as.numeric(date) + confirmed + holidays + dominant_variant,
        data = training(splits) )

models_tbl = modeltime_table(
    model_fit_arima,
    model_fit_arima_xreg,
    model_fit_arima_boosted,
    model_fit_prophet,
    model_fit_lm
)

models_tbl
## # Modeltime Table
## # A tibble: 5 × 3
##   .model_id .model   .model_desc                                  
##       <int> <list>   <chr>                                        
## 1         1 <fit[+]> ARIMA(2,1,3)(2,0,0)[7]                       
## 2         2 <fit[+]> REGRESSION WITH ARIMA(1,0,0)(1,0,0)[7] ERRORS
## 3         3 <fit[+]> ARIMA(5,1,1)(0,0,2)[7] W/ XGBOOST ERRORS     
## 4         4 <fit[+]> PROPHET W/ REGRESSORS                        
## 5         5 <fit[+]> LM

If this data is close to your data, you can see that the first non-seasonal difference was taken in the first and third auto.arima() models. This answers the question from Allison today.

7.2 Training Performance

7.2.1 Plotting the Residuals

The code below is used to plot the residuals for each of the models. The first three lines of code are very similar to the code shown in the package vignette. The additional code is to allow you to customize the plot to your liking.

models_tbl %>%
    modeltime_calibrate(new_data = training(splits)) %>%
    modeltime_residuals() %>%
    plot_modeltime_residuals(.interactive = FALSE,
                             .type = 'timeplot') +
  scale_x_date(breaks = scales::pretty_breaks(12)) +
  scale_color_brewer(palette = 'Dark2') +
  facet_wrap(~ .model_desc, ncol = 1, scales = 'free') +
  theme_bw() +
  theme(legend.position = 'none') +
  labs(
    title = 'Residuals plot for the five models based on our training data',
    subtitle = 'The residuals are large on the same day irrespective of model')

7.2.2 Statistical Tests for the Residuals

The modeltime_residuals_test() computes the results from 4 different statistical tests:

  • Shapiro-Wilk Test tests the Normality of the residuals. The Null Hypothesis is that the residuals are normally distributed. A low p-value below a given significance level indicates the values are NOT Normally Distributed.

  • Both the Box-Pierce and Ljung-Box Tests are used to test for the absence of autocorrelation in residuals. A low p-value below a given significance level indicates the values are autocorrelated.

models_tbl %>%
    modeltime_calibrate(new_data = training(splits)) %>%
    modeltime_residuals() %>%
  modeltime_residuals_test()
## # A tibble: 5 × 6
##   .model_id .model_desc          shapiro_wilk box_pierce ljung_box durbin_watson
##       <int> <chr>                       <dbl>      <dbl>     <dbl>         <dbl>
## 1         1 ARIMA(2,1,3)(2,0,0)…     1.59e-29    0.880     0.880            2.01
## 2         2 REGRESSION WITH ARI…     4.54e-31    0.957     0.957            1.99
## 3         3 ARIMA(5,1,1)(0,0,2)…     2.34e-28    0.786     0.786            2.02
## 4         4 PROPHET W/ REGRESSO…     4.34e-30    0.0586    0.0579           1.83
## 5         5 LM                       1.07e-30    0.00212   0.00205          1.73
# If you want to explore the residuals seperately, they are stored as follows
training_residuals = models_tbl %>%
    modeltime_calibrate(new_data = training(splits)) %>%
    modeltime_residuals() 

The results above indicate that the residuals are not normally distributed. However, they also indicate that no significant autocorrelation is exhibited in the residuals as indicated by both the Box-Pierce and Ljung-Box test results.

7.2.3 Model’s Training Performance

Multiple accuracy measures can be computed as follows.

models_tbl %>%
    modeltime_calibrate(new_data = training(splits)) %>%
    modeltime_accuracy() %>%
    table_modeltime_accuracy(
    )

Note that MAPE is infinite in the above table since there are several days where the count of new deaths = 0. The MASE indicates that all models improve over the naive forecast by about 25%. However, the rsq for all models is low. May be we need to add additional predictors.


8 Testing Performance

The name of the function from the modeltime package is somewhat confusing. It is called calibrate but calibrating adds a new column, .calibration_data, with the test predictions and residuals inside. A few notes on Calibration:

  • Calibration is how confidence intervals and accuracy metrics are determined
  • Calibration Data is simply forecasting predictions and residuals that are calculated from out-of-sample data.
  • After calibrating, the calibration data follows the data through the forecasting workflow.
calibration_tbl = models_tbl %>%
    modeltime_calibrate( new_data = testing(splits) )

calibration_tbl
## # Modeltime Table
## # A tibble: 5 × 5
##   .model_id .model   .model_desc                          .type .calibration_da…
##       <int> <list>   <chr>                                <chr> <list>          
## 1         1 <fit[+]> ARIMA(2,1,3)(2,0,0)[7]               Test  <tibble>        
## 2         2 <fit[+]> REGRESSION WITH ARIMA(1,0,0)(1,0,0)… Test  <tibble>        
## 3         3 <fit[+]> ARIMA(5,1,1)(0,0,2)[7] W/ XGBOOST E… Test  <tibble>        
## 4         4 <fit[+]> PROPHET W/ REGRESSORS                Test  <tibble>        
## 5         5 <fit[+]> LM                                   Test  <tibble>

Per the description above the code chunk, the model descriptions did not change and we only appended the testing data.

8.1 Holdout Performance

8.1.1 Plotting the Residuals

models_tbl %>%
    modeltime_calibrate(new_data = testing(splits)) %>%
    modeltime_residuals() %>%
    plot_modeltime_residuals(.interactive = FALSE,
                             .type = 'timeplot') +
  scale_x_date(breaks = scales::pretty_breaks(12)) +
  scale_color_brewer(palette = 'Dark2') +
  facet_wrap(~ .model_desc, ncol = 1) +
  theme_bw() +
  theme(legend.position = 'none') +
  labs(
    title = 'Residuals plot for the five models based on our holdout data',
    subtitle = 'The ARIMA type models are most accurate. LM is over-predicting since residuals are mostly negative') 

8.1.2 Statistical Tests for the Residuals

models_tbl %>%
    modeltime_calibrate(new_data = testing(splits)) %>%
    modeltime_residuals() %>%
  modeltime_residuals_test()
## # A tibble: 5 × 6
##   .model_id .model_desc          shapiro_wilk box_pierce ljung_box durbin_watson
##       <int> <chr>                       <dbl>      <dbl>     <dbl>         <dbl>
## 1         1 ARIMA(2,1,3)(2,0,0)…  0.00000149      0.215     0.203          1.97 
## 2         2 REGRESSION WITH ARI…  0.000156        0.0494    0.0438         0.432
## 3         3 ARIMA(5,1,1)(0,0,2)…  0.000000585     0.475     0.464          2.10 
## 4         4 PROPHET W/ REGRESSO…  0.00680         0.148     0.138          1.59 
## 5         5 LM                    0.0000257       0.215     0.204          0.382

The holdout results are consistent with the observations made on the training data. One exception is that the ARIMA with xreg (model 2) shows correlated residuals on the holdout dataset.

8.1.3 Model’s Holdout Performance

Note that MAPE is infinite in the below table since there are several days where the count of new deaths = 0

models_tbl %>%
    modeltime_calibrate(new_data = testing(splits)) %>%
    modeltime_accuracy() %>%
    table_modeltime_accuracy(
    )

The first and third models continue to be the best models. However, their improvement over the naive forecast (i.e., random walk) is smaller than the training dataset.


  1. Email: | Phone: +1-513-529-4185 | Website: Miami University Official↩︎

LS0tDQp0aXRsZTogIk1vZGVsaW5nIENPVklELTE5IERlYXRocyBpbiBTYWludCBMb3VpcyBhcyBhIEZ1bmN0aW9uIG9mIENhc2VzIg0KYXV0aG9yOg0KICAtIG5hbWU6ICJGYWRlbCBNLiBNZWdhaGVkIF5bRW1haWw6IGZtZWdhaGVkQG1pYW1pb2guZWR1IHwgUGhvbmU6ICsxLTUxMy01MjktNDE4NSB8IFdlYnNpdGU6IDxhIGhyZWY9XCJodHRwczovL21pYW1pb2guZWR1L2ZzYi9kaXJlY3RvcnkvP3VwPS9kaXJlY3RvcnkvbWVnYWhlZm1cIj5NaWFtaSBVbml2ZXJzaXR5IE9mZmljaWFsPC9hPl0iDQogICAgYWZmaWxpYXRpb246IEZhcm1lciBTY2hvb2wgb2YgQnVzaW5lc3MsIE1pYW1pIFVuaXZlcnNpdHkNCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVCICVkLCAlWScpYCINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY3NzOiBodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vZm1lZ2FoZWQvdmFjY2luZXMvbWFpbi9Db2RlL2N1c3RvbS5jc3MNCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cNCiAgICBjb2RlX2Rvd25sb2FkOiBUUlVFDQogICAgbnVtYmVyX3NlY3Rpb25zOiBUUlVFDQogICAgcGFnZWRfZGY6IFRSVUUNCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRSVUUNCiAgICB0aGVtZTogcmVhZGFibGUNCiAgaW5jbHVkZXM6DQogICAgaW5faGVhZGVyOiBodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vZm1lZ2FoZWQvdmFjY2luZXMvbWFpbi9Db2RlL3N0cnVjdHVyZS50ZXgNCi0tLQ0KDQoNCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoY2FjaGUgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgIGVjaG8gPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgIHdhcm5pbmcgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgcHJvZ3Jlc3MgPSBGQUxTRSwgDQogICAgICAgICAgICAgICAgICAgICAgdmVyYm9zZSA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIGRldiA9ICdwbmcnLA0KICAgICAgICAgICAgICAgICAgICAgIGZpZy5yZXRpbmEgPSAyLA0KICAgICAgICAgICAgICAgICAgICAgIG91dC53aWR0aCA9ICcxMDAlJykNCg0KIyBpbnN0YWxsaW5nIHRoZSByZW1vdGVzIGFuZCBpY29ucyBwYWNrYWdlcyBpZiBuZWVkZWQNCmlmKCFyZXF1aXJlKHJlbW90ZXMpKSB7aW5zdGFsbC5wYWNrYWdlcygncmVtb3RlcycpOyBsaWJyYXJ5KHJlbW90ZXMpfQ0KDQppZighcmVxdWlyZShpY29ucykpew0KICByZW1vdGVzOjppbnN0YWxsX2dpdGh1YigibWl0Y2hlbGxvaGFyYXdpbGQvaWNvbnMiKQ0KICBsaWJyYXJ5KGljb25zKQ0KICBkb3dubG9hZF9mb250YXdlc29tZSgpDQogIGRvd25sb2FkX2FjYWRlbWljb25zKCkNCn0gDQoNCg0KIyBTZXR0aW5nIHRoZSByYW5kb20gc2VlZCBhbmQgY2h1bmsgZGVwZW5kZW5jaWVzDQprbml0cjo6b3B0c19jaHVuayRzZXQoY2FjaGUuZXh0cmEgPSBzZXQuc2VlZCgyMDIyKSwNCiAgICAgICAgICAgICAgICAgICAgICBhdXRvZGVwID0gVFJVRSkgDQoNCg0KYGBgDQoNCiMgUHJlZmFjZSANCg0KKipTb21lIFIgc3R1ZmY6KiogICANCg0KLSBZb3UgY2FuIGRvd25sb2FkIHRoZSBlbnRpcmUgUm1kIGZvciB0aGlzIE1hcmtkb3duIGJ5IGNsaWNraW5nIG9uIHRoZSB1cHBlciByaWdodCBgQ29kZWAgYnV0dG9uLiAgDQotIFlvdSBjYW4gYWxzbyB1c2UgdGhlIHVwcGVyIHJpZ2h0IGBDb2RlYCBidXR0b24gdG8gaGlkZSBhbGwgY29kZSBpZiB5b3UganVzdCB3YW50IHRvIGZvY3VzIG9uIHRoZSBhbmFseXNpcy4gIA0KDQpHaXZlbiB0aGF0IHdlIGNhbm5vdCBhY2Nlc3MgdGhlIGRhdGEgYW5kIEkgd2FzIHVuZGVyIHRoZSBpbXByZXNzaW9uIHRoYXQgb3VyIGNvZGluZyB3YWxrLXRocm91Z2ggdG9kYXkgd2FzIHNvbWV3aGF0IGhlbHBmdWwsIEkgYXR0ZW1wdGVkIHRvIGNyZWF0ZSBhIHRlbXBsYXRlIGZvciB0aGUgYW5hbHlzaXMgdGhhdCB5b3UgY2FuIHVzZSBmb3IgdGhlIGRhdGEuIEkgd2FzIG5vdCBhYmxlIHRvIGFjY2VzcyBgaG9zcGl0YWxpemF0aW9uc2Agb24gdGhlIGNvdW50eSBsZXZlbCBmcm9tIHRoZSBbQ292aWQxOSBEYXRhSHViXShodHRwczovL2NvdmlkMTlkYXRhaHViLmlvLykgc28gSSB3aWxsIGJlIHVzaW5nIGBkZWF0aHNgIGlubGlldSBvZiBgaG9zcGl0YWxpemF0aW9uc2AgZm9yIHRoaXMgd2Fsay10aHJvdWdoLiANCg0KVGhpcyAqKnRlbXBsYXRlIG1ha2VzIHRoZSBmb2xsb3dpbmcgYXNzdW1wdGlvbnM6KiogIA0KDQotIFRoZSBDT1ZJRC0xOSBEYXRhSHViIHByb3ZpZGVzIHRoZSBkYXRhIGF0IHRocmVlIHVuaXRzIG9mIGFnZ3JlZ2F0aW9uOiBgY291bnRyeWAsIGBzdGF0ZWAsIGFuZCBgY291bnR5YC4gRnJvbSBteSB1bmRlcnN0YW5kaW5nLCB5b3UgYXJlIG1vZGVsaW5nIGRhdGEgZm9yIEdyZWF0ZXIgU3QuIExvdWlzIChpLmUuLCBTdC4gTG91aXMgTVNBKS4gVG8gZ2V0IHRoZSBkYXRhIHNpbWlsYXIgdG8gd2hhdCBQZXRlciBoYXMgYWNjZXNzIHRvLCBJIHN1bW1lZCBib3RoIGNhc2VzIGFuZCBkZWF0aHMgZm9yIGNvdW50aWVzIGluIGJvdGggSWxsaW5vaXMgKCdCb25kJywgJ0NhbGhvdW4nLCAnQ2xpbnRvbicsICdKZXJzZXknLCAnTWFjb3VwaW4nLCAnTWFkaXNvbicsICdNb25yb2UnKSBhbmQgTWlzc291cmkgKCdDcmF3Zm9yZCcsICdGcmFua2xpbicsICdKZWZmZXJzb24nLCAnTGluY29sbicsICdTdC4gQ2hhcmxlcycsICdTdC4gQ2xhaXInLCAnU3QuIExvdWlzJywgJ1N0LiBMb3VpcyBDaXR5JywgJ1dhcnJlbicpIGJhc2VkIG9uIHRoZSBbR3JlYXRlciBTdC4gTG91aXMgV2lraXBlZGlhIFBhZ2VdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0dyZWF0ZXJfU3QuX0xvdWlzKS4gICANCg0KLSBPbiB0b3Agb2YgbXkgaGVhZCwgSSBkaWQgbm90IHJlbWVtYmVyIHRoZSBzdHVkeSBwZXJpb2QgdGhhdCB5b3UgaGFkIGZvciB0aGUgYW5hbHlzaXMuIFNvIEkgaGF2ZSB1c2VkIGRhdGEgc3RhcnRpbmcgZnJvbSBgSmFuIDA0LCAyMDIxYCAoZmlyc3QgTW9uZGF5IG9mIHRoZSBZZWFyKSBmb3IgdGhpcyBhbmFseXNpcy4gIA0KDQotIEZvciB0aGUgU3QuIExvdWlzIEhlYWx0aCBEZXBhcnRtZW50LCBJIGFzc3VtZWQgdGhhdCB0aGUgbm9uX3dlZWtlbmQgaG9saWRheXMgbWF0Y2hlZCB0aG9zZSBvZiB0aGUgTmV3IFlvcmsgU3RvY2sgRXhjaGFuZ2UuIFRoZXNlIHdlcmUgZXh0cmFjdGVkIHVzaW5nICBgSE9MSURBWV9TRVFVRU5DRShzdGFydF9kYXRlID0gJzIwMjEtMDEtMDQnLCBlbmRfZGF0ZSA9ICcyMDIyLTA4LTAxJywgY2FsZW5kYXIgPSAnTllTRScpYCBmcm9tIHRoZSBbdGlkeXF1YW50IHBhY2thZ2VdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy90aWR5cXVhbnQvdGlkeXF1YW50LnBkZikuIFRoZSBleGFjdCBob2xpZGF5IGRhdGVzIGZyb20gdGhpcyBmdW5jdGlvbiBjYWxsIGFyZTogYHIgdGlkeXF1YW50OjpIT0xJREFZX1NFUVVFTkNFKHN0YXJ0X2RhdGUgPSAnMjAyMS0wMS0wNCcsIGVuZF9kYXRlID0gCScyMDIyLTA4LTAxJywgY2FsZW5kYXIgPSAnTllTRScpYC4gIA0KDQotIEkga2luZCBvZiBgZXllYmFsbGVkIHRoZSBkYXRlcyBmb3IgdGhlIGRvbWluYW50IENPVklELTE5IHZhcmlhbnRgIGJhc2VkIG9uIHRoZSBbZm9sbG93aW5nIE5ZIFRpbWVzIEFydGljbGVdKGh0dHBzOi8vd3d3Lm55dGltZXMuY29tL2ludGVyYWN0aXZlLzIwMjEvaGVhbHRoL2Nvcm9uYXZpcnVzLXZhcmlhbnQtdHJhY2tlci5odG1sKS4gSSBoYXZlIHVzZWQgdGhlIHBsb3Qgd2l0aGluIHRoZSBhcnRpY2xlIHRvIGRlZmluZSB0aGUgZG9taW5hbnQgdmFyaWFudCB0byBiZSBhcyBmb2xsb3dzOiBgZG9taW5hbnRfdmFyaWFudCA9IGNhc2Vfd2hlbihkYXRlIDwgeW1kKCcyMDIxLTAzLTAxJykgfiAnRXBzaWxvbicsIGRhdGUgPCB5bWQoJzIwMjEtMDYtMTUnKSB+ICdBbHBoYScsIGRhdGUgPCB5bWQoJzIwMjEtMTItMTUnKSB+ICdEZWx0YScsIGRhdGUgPj0geW1kKCcyMDIxLTEyLTE1JykgfiAnT21pY3JvbicpICU+JSBhc19mYWN0b3IoKWAuIFRoaXMgYWxsb3dlZCBtZSB0byB0YWtlIGludG8gYWNjb3VudCB0aGUgJ2VudGlyZScgdGltZS1zZXJpZXMgcmF0aGVyIHRoYW4ganVzdCB0aGUgYE9taWNyb25gIHBhcnQuICANCg0KLSBJIGFzc3VtZWQgdGhhdCBvbmUgb2YgdGhlIGVuZCBnb2FscyBmcm9tIHlvdXIgYW5hbHlzaXMgd291bGQgYmUgKipmb3JlY2FzdGluZyoqIGZ1dHVyZSBob3NwaXRhbGl6YXRpb25zLiBTbyBmYXIsIGluIG91ciBtZWV0aW5ncywgd2UgZGlzY3Vzc2VkIGV4cGxhbmF0b3J5IG1vZGVsaW5nLiBTbyBJIGRpdmlkZWQgbXkgZGF0YSBpbnRvIGEgdHJhaW5pbmcgYW5kIGEgaG9sZG91dCBzYW1wbGUgLS0gcHJpbWFyaWx5IGZvciB0aGUgcHVycG9zZSBvZiBzaG93aW5nIHlvdSBob3cgaXRzIGRvbmUuIDxzcGFuIHN0eWxlPSJjb2xvcjpyZWQ7Zm9udC13ZWlnaHQ6Ym9sZCI+T2J2aW91c2x5LCB5b3UgY2FuIG92ZXJyaWRlIHRoaXMgYnkgaWdub3JpbmcgdGhlIGhvbGRvdXQgYW5kIGp1c3QgZm9jdXNpbmcgb24gdGhlIHRyYWluaW5nIHJlc3VsdHMgKHdoaWNoIHlvdSBjYW4gYWxzbyBzZXQgdG8gYmUgdGhlIGVudGlyZSB0aW1lIHBlcmlvZCBvZiBpbnRlcmVzdCkuPC9zcGFuPiAgDQoNCi0gKipJIGRpZCBub3QgbGFnIHRoZSBjb25maXJtZWQgY2FzZXMuKiogSSBiZWxpZXZlIHlvdSBzaG91bGQgdGVzdCBvdXQgZGlmZmVyZW50IGxhZ3Mgd2l0aGluIHRoZSBjb250ZXh0IG9mIGJvdGggdGhlICoqYXV0by5hcmltYSgpKiogYW5kIHRoZSBvdGhlciBtb2RlbHMgc2luY2Ugd2Ugd291bGQgYmUgaHlwb3RoZXNpemluZyB0aGF0IHRoZSBsYWdnZWQgY29uZmlybWVkIGNhc2VzIHdvdWxkIGxlYWQgdG8gZnV0dXJlIGhvc3BpdGFsaXphdGlvbnMuICoqSXQgaXMgbXkgdW5kZXJzdGFuZGluZyB0aGF0IHRoZSBhdXRvLmFyaW1hKCkgZG9lcyBub3QgYWNjb3VudCBmb3IgdGhlIGxhZ2dlZCByZWxhdGlvbnNoaXBzIG9uIHRoZSBleG9nZW5vdXMgdmFyaWFibGVzLioqIFNlZSB0aGUgW0xhZ2dlZCBQcmVkaWN0b3JzIGluIHRoZSBmcHAyIGJvb2tdKGh0dHBzOi8vb3RleHRzLmNvbS9mcHAyL2xhZ2dlZC1wcmVkaWN0b3JzLmh0bWwpLiA8c3BhbiBzdHlsZT0iY29sb3I6cmVkO2ZvbnQtd2VpZ2h0OmJvbGQiPlRoZSBpbXBsaWNhdGlvbnMgb2YgaW50cm9kdWNpbmcgdGhlIGxhZ2dlZCBwcmVkaWN0b3JzIG9uIHRoZSBjdXJyZW50IGNvZGUgYXJlIGFzIGZvbGxvd3M6PC9zcGFuPiAgIA0KICAqIFRoZSBsYWcgY2FuIGJlIGNvbXB1dGVkIGFzIGZvbGxvd3Mgd2l0aGluIHRoZSBtdXRhdGUgZnVuY3Rpb246IA0KICBgY29uZmlybWVkMSA9IGxhZyhjb25maXJtZWQsIGsgPSAxKWAgZm9yIGxhZzEuIENoYW5naW5nIGBrYCB3b3VsZCBjaGFuZ2UgdGhlIGxhZy4gIA0KICAqIFRoZSBsYWcgc2hvdWxkIGFsc28gYmUgYWNjb3VudGVkIGZvciBpbiB0aGUgYGluaXRpYWxfdGltZV9zcGxpdCgpYCB0byBlbnN1cmUgYSBzZXBlcmF0aW9uIGJldHdlZW4gdGhlIHRyYWluaW5nIGFuZCB0ZXN0aW5nIGRhdGFzZXQuIEluIHRoYXQgZnVuY3Rpb24sIHRoZSBkZWZhdWx0IHZhbHVlIGZvciBgbGFnID0gMGAsIGFuZCB5b3Ugd291bGQgbmVlZCB0byBjaGFuZ2UgaXQgdG8gdGhlIGhpZ2hlc3QgbGFnIHlvdSBhcmUgZXZhbHVhdGluZy4gDQoNCi0gSSAqKmlnbm9yZWQgcG90ZW50aWFsbHkgcmVsZXZhbnQgcHJlZGljdG9ycyoqIHN1Y2ggYXMgdGhlIGBzdHJpbmdlbmN5X2luZGV4YCBhbmQgYHBlb3BsZV9mdWxseV92YWNjaW5hdGVkYC4gVGhlc2UgYXJlIGZvdW5kIGluIHRoZSBgY292aWRfdGJsYCBidXQgSSBkaWQgbm90IGFnZ3JlZ2F0ZSB0aGVtIGZvciB0aGUgYFN0LiBMb3VpcyBNU0FgLiBTbyB5b3UgY2FuIGVhc2lseSBpbmNsdWRlIHRoZW0gYnkgYWRkaW5nIHRoZW0gdG8gYm90aCB0aGUgYHNlbGVjdCgpYCB1c2VkIHRvIGNyZWF0ZSBgc3RfbG91aXNfYWdnX3RibGAgaW4gU2VjdGlvbiAzLCBhbmQgdGhlbiBzdW1taW5nIG92ZXIgdGhlbSB3aXRoIHRoZSBgc3VtbWFyaXNlX2F0KClgIHVzZWQgbGF0ZXIgaW4gdGhlIGNvZGUuIE90aGVyICoqcG90ZW50aWFsIHByZWRpY3RvcnMgaW5jbHVkZToqKiAoYSkgYW4gaW5kaWNhdG9yIHZhcmlhYmxlIGluZGljYXRpbmcgdGhlIGVtZXJnZW5jZSBvZiBhIG5ldyB2YXJpYW50IChlLmcuLCB0aGlzIHZhcmlhYmxlIGNhbiBoYXZlIGEgYHllc2AgaW4gdGhlIDIgd2Vla3MgYmVmb3JlIGFuZCBhZnRlciB3ZSBzd2l0Y2ggdG8gYSBuZXcgZG9taW5hbnQgdmFyaWFibGUgaW4gb3VyIGNvZGUpOyAoYikgbGVhZGluZyB0aGUgaG9saWRheSB2YXJpYWJsZSAod2l0aCBsYWdzIHVwIHRvIDEtMyB3ZWVrcyksIHdpdGggdGhlIGh5cG90aGVzaXMgdGhhdCBnYXRoZXJpbmdzIGFuZCB0cmF2ZWwgZHVyaW5nIGEgaG9saWRheSB3aWxsIGhhdmUgZnV0dXJlIGVmZmVjdHMgb24gaG9zcGl0YWxpemF0aW9ucyAoc2VlIHRoZSBgbGVhZCgpYCBmdW5jdGlvbiB3aGljaCBoYXMgdGhlIGV4YWN0IHNhbWUgc3ludGF4IGFzIHRoZSBgbGFnKClgIGZ1bmN0aW9uKTsgYW5kIChjKSBsYWdnaW5nIGluZGljYXRvcnMgb2YgYHBlcmNlbnQgb2YgcG9zaXRpdmUgdGVzdHNgLCBpLmUuLCBhIHNwaWtlIGluIHRoZSBwZXJjZW50IG9mIHBvc2l0aXZlIHRlc3RzIHdoaWNoIG1heSBiZSBtb3JlIGltcG9ydGFudCB0aGFuIHRoZSBjb25maXJtZWQgY2FzZXMgd2l0aCBsYXR0ZXIgd2F2ZXMgc2luY2UgbW9yZSBwZW9wbGUgYXJlIHRlc3RpbmcgYXQgaG9tZS4gPHNwYW4gc3R5bGU9ImNvbG9yOnJlZDtmb250LXdlaWdodDpib2xkIj5UaGVzZSB3b3VsZCBiZSBuaWNlIHRvIGV4cGxvcmUgYXMgeW91IGNvbnRpbnVlIHRvIGJ1aWxkIHRoZSBtb2RlbC48L3NwYW4+IA0KDQoNCi0tLQ0KDQojIFF1ZXN0aW9ucyBmb3IgdGhlIEdyb3VwIEJhc2VkIG9uIE15IFByZWxpbWluYXJ5IEFuYWx5c2lzIEJlbG93DQoNCi0gKipIb3cgc2hvdWxkIHdlIGhhbmRsZSBuZWdhdGl2ZSBjb3VudHMgaW4gY2FzZXMvZGVhdGhzL2hvc3BpdGFsaXphdGlvbnM/KiogU2VlIHRoZSBkZWF0aCBjb3VudCBvbiBgTWFyY2ggMjksIDIwMjFgIGluIHRoZSBwbG90IGluIFNlY3Rpb24gNS4xIGFzIGFuIGV4YW1wbGUuICANCg0KLSAqKlNob3VsZCB3ZSB3b3JyeSBhYm91dCB0aGUgcmVzaWR1YWxzJyBkZXBhcnR1cmUgZnJvbSBub3JtYWxpdHk/KiogU2VlIHRoZSByZXN1bHRzIG9mIHRoZSBgU2hhcGlyby1XaWxrIFRlc3RgIGluIFNlY3Rpb24gNy4yLjIuIA0KDQotICoqRG8geW91IGFncmVlIHdpdGggbXkgY29tbWVudCBhYm91dCBsYWdnZWQgcHJlZGljdG9ycz8qKiBTZWUgdGhlIHNlY29uZCB0byBsYXN0IGFzc3VtcHRpb24gaW4gdGhlIFByZWZhY2Ugc2VjdGlvbi4gIA0KDQotICoqSG93IG11Y2ggd2VpZ2h0IHNob3VsZCB3ZSBwdXQgb24gdGhlIGZhY3QgdGhhdCB0aGUgcnNxIG9mIGFsbCBtb2RlbHMgYXJlIHNtYWxsPyoqIFNlZSB0aGUgbGFzdCBjb2x1bW4gKHNjcm9sbGFibGUpIGluIHRoZSB0YWJ1bGF0ZWQgdHJhaW5pbmcgYW5kIHRlc3RpbmcgcmVzdWx0cyBpbiBTZWN0aW9ucyA3IGFuZCA4Lg0KDQoNCi0tLQ0KDQoNCiMgUmVxdWlyZWQgUGFja2FnZXMNCg0KVGhlIGNvZGUgY2h1bmsgYmVsb3cgY29udGFpbnMgdGhlIHBhY2thZ2VzIHVzZWQgaW4gdGhpcyBhbmFseXNpcw0KDQpgYGB7ciBwa2dzfQ0KIyB0aWR5dmVyc2UgcGtnIGZvciBkYXRhIG1hbmlwdWxhdGlvbg0KaWYoIXJlcXVpcmUodGlkeXZlcnNlKSkge2luc3RhbGwucGFja2FnZXMoJ3RpZHl2ZXJzZScpOyBsaWJyYXJ5KHRpZHl2ZXJzZSl9DQoNCiMgcmVhZHIgZm9yIHJlYWRpbmcgZmlsZXMNCmlmKCFyZXF1aXJlKHJlYWRyKSkge2luc3RhbGwucGFja2FnZXMoJ3JlYWRyJyk7IGxpYnJhcnkocmVhZHIpfQ0KDQojIGZvcmVjYXN0IHBhY2thZ2UgZm9yIHNvbWUgcXVpY2sgZGlzcGxheXMgYW5kIGFuYWx5c2VzDQppZighcmVxdWlyZShmb3JlY2FzdCkpIHtpbnN0YWxsLnBhY2thZ2VzKCdmb3JlY2FzdCcpOyBsaWJyYXJ5KGZvcmVjYXN0KX0NCg0KIyB0aW1ldGsgZm9yIHRpZHkgdGltZS1zZXJpZXMgcHJlcHJvY2Vzc2luZw0KaWYoIXJlcXVpcmUodGltZXRrKSkge2luc3RhbGwucGFja2FnZXMoJ3RpbWV0aycpOyBsaWJyYXJ5KHRpbWV0ayl9DQoNCiMgdGlkeXF1YW50IGZvciB0aWR5IHRpbWUtc2VyaWVzIGFuYWx5c2lzDQppZighcmVxdWlyZSh0aWR5cXVhbnQpKSB7aW5zdGFsbC5wYWNrYWdlcygndGlkeXF1YW50Jyk7IGxpYnJhcnkodGlkeXF1YW50KX0NCg0KIyBtb2RlbHRpbWUgZm9yIHRpZHkgZm9yZWNhc3RpbmcNCmlmKCFyZXF1aXJlKG1vZGVsdGltZSkpIHtpbnN0YWxsLnBhY2thZ2VzKCdtb2RlbHRpbWUnKTsgbGlicmFyeShtb2RlbHRpbWUpfQ0KDQojIGZvciBpbnRlcmFjdGl2ZSBwbG90cw0KaWYoIXJlcXVpcmUocGxvdGx5KSkge2luc3RhbGwucGFja2FnZXMoJ3Bsb3RseScpOyBsaWJyYXJ5KHBsb3RseSl9DQoNCiMgZm9yIHRpZHkgbWFjaGluZSBsZWFybmluZw0KaWYoIXJlcXVpcmUodGlkeW1vZGVscykpIHtpbnN0YWxsLnBhY2thZ2VzKCd0aWR5bW9kZWxzJyk7IGxpYnJhcnkodGlkeW1vZGVscyl9DQoNCiMgZm9yIHRoZSB4Z2Jvb3N0IG1vZGVsDQppZighcmVxdWlyZSh4Z2Jvb3N0KSkge2luc3RhbGwucGFja2FnZXMoJ3hnYm9vc3QnKTsgbGlicmFyeSh4Z2Jvb3N0KX0NCmBgYA0KDQoNCi0tLQ0KDQojIEV4dHJhY3RpbmcgQ09WSUQtMTkgRGF0YQ0KDQpQZXIgdGhlIFByZWZhY2UsIEkgcmVhZCB0aGUgZGF0YSB1c2VkIGluIHRoaXMgYW5hbHlzaXMgZnJvbSB0aGUgW0NPVklELTE5IERhdGFIdWJdKGh0dHBzOi8vY292aWQxOWRhdGFodWIuaW8vYXJ0aWNsZXMvZGF0YS5odG1sKS4gVG8gbm90IHJlcXVpcmUgdGhlIGluc3RhbGxpbmcgb2YgdGhlIGBDT1ZJRDE5YCBwYWNrYWdlLCBJIGhhdmUgZG93bmxvYWRlZCB0aGUgemlwIGZpbGUgYW5kIHVuemlwcGVkIHVzaW5nIFIuIFRoZW4sIEkgZmlsdGVyZWQgdGhlIGRhdGEgdG8gTWlzc291cmkgYW5kIElsbGlub2lzLCBzZWxlY3RlZCB0aGUgYXBwcm9wcmlhdGUgY291bnRpZXMsIGFuZCBjb21wdXRlZCB0aGUgdmFyaWFibGVzIHRoYXQgd2UgYXJlIGludGVyZXN0ZWQgaW4uDQoNCmBgYHtyIHJhd19kYXRhfQ0KIyBjcmVhdGluZyBhIHRlbXAgZmlsZSBmb3IgZG93bmxvYWRpbmcgdGhlIGRhdGENCnRlbXAgPSB0ZW1wZmlsZSgpIA0KDQojIGRvd25sb2FkIHRoZSBmaWxlIHRvIHRlbXBvcmFyeSBsb2NhdGlvbg0KZG93bmxvYWQuZmlsZSgiaHR0cHM6Ly9zdG9yYWdlLmNvdmlkMTlkYXRhaHViLmlvL2NvdW50cnkvVVNBLmNzdi56aXAiLCB0ZW1wKQ0KDQojIHVuemlwIGFuZCByZWFkIHRoZSBmaWxlDQpjb3ZpZF90YmwgPSB1bnoodGVtcCwgIlVTQS5jc3YiKSAlPiUgDQogICMgcmVhZGluZyB0aGUgZGF0YSBmcm9tIHRoZSBDU1YNCiAgcmVhZF9jc3YoKSAlPiUgDQogICMgZmlsdGVyaW5nIHRvIE1pc3NvdXJpIGFuZCBJbGxpbm9pcw0KICBmaWx0ZXIoYWRtaW5pc3RyYXRpdmVfYXJlYV9sZXZlbF8yICVpbiUgYygnSWxsaW5vaXMnLCAnTWlzc291cmknKSApDQoNCg0Kc3RfbG91aXNfdGJsID0gY292aWRfdGJsICU+JSANCiAgIyBSZWxldmFudCBDb3VudGllcyBpbiBJbGxub2lzDQogIGZpbHRlciggDQogICAgKGFkbWluaXN0cmF0aXZlX2FyZWFfbGV2ZWxfMiA9PSAnSWxsaW5vaXMnICYNCiAgICAgICBhZG1pbmlzdHJhdGl2ZV9hcmVhX2xldmVsXzMgJWluJSBjKCdCb25kJywgJ0NhbGhvdW4nLCAnQ2xpbnRvbicsICdKZXJzZXknLCAnTWFjb3VwaW4nLCAnTWFkaXNvbicsICdNb25yb2UnKSApIHwgDQogICAgICAjIFJlbGV2YW50IENvdW50aWVzIGluIE1pc3NvdXJpIA0KICAgICAgKGFkbWluaXN0cmF0aXZlX2FyZWFfbGV2ZWxfMiA9PSAnTWlzc291cmknICYNCiAgICAgICAgIGFkbWluaXN0cmF0aXZlX2FyZWFfbGV2ZWxfMyAlaW4lIGMoJ0NyYXdmb3JkJywgJ0ZyYW5rbGluJywgJ0plZmZlcnNvbicsICdMaW5jb2xuJywgJ1N0LiBDaGFybGVzJywgJ1N0LiBDbGFpcicsICdTdC4gTG91aXMnLCAnU3QuIExvdWlzIENpdHknLCAnV2FycmVuJykpDQogICkNCg0KIyBBZ2dyZWdhdGluZyB0aGUgY291bnRzIGJ5IGRheSAoc28gdGhhdCB3ZSBoYXZlIGFuIGFwcHJveGltYXRpb24gb2YgdG90YWwgbnVtYmVycyBmb3Igc3QuIGxvdWlzKQ0Kc3RfbG91aXNfYWdnX3RibCA9IA0KICBzdF9sb3Vpc190YmwgJT4lIA0KICAjIGdyb3VwaW5nIGJ5IGRhdGUgc28gd2UgY2FuIGNyZWF0ZWQgYW4gYWdncmVnYXRlZCBzdW1tYXRpb24gYWNyb3NzIGFsbCBjb3VudGllcw0KICBncm91cF9ieShkYXRlKSAlPiUgDQogIHNlbGVjdChkYXRlLCANCiAgICAgICAgICMgdmFyaWFibGVzIHRvIGJlIHN1bW1lZCBhY3Jvc3MgYWxsIGNvdW50aWVzIGluIFN0LiBMb3Vpcw0KICAgICAgICAgY29uZmlybWVkLCBkZWF0aHMsIHJlY292ZXJlZCwgaG9zcCwgaWN1LCB2ZW50LCANCiAgICAgICAgICMgcG9wdWxhdGlvbiB3aWxsIGJlIHN1bW1lZCBvbmx5IHRvIHNlZSBpZiB0aGUgYWdncmVnYXRpb24gaXMgcmVhc29uYWJsZQ0KICAgICAgICAgIyBpLmUuLCBpcyB0aGUgdG90YWwgcG9wIGNsb3NlIHRvIHRoZSBhY3R1YWwgZm9yIHRoZSBTdC4gTG91aXMgTVNBDQogICAgICAgICBwb3B1bGF0aW9uLA0KICAgICAgICAgIyBvdGhlciB2YXJpYWJsZXMgdGhhdCBjYW4gYmUgaW5jbHVkZWQgaW4gdGhlIGFuYWx5c2lzIGxhdGVyDQogICAgICAgICBzdHJpbmdlbmN5X2luZGV4KSAlPiUgDQogIHN1bW1hcmlzZV9hdCgNCiAgICB2YXJzKGNvbmZpcm1lZCwgZGVhdGhzLCByZWNvdmVyZWQsIGhvc3AsIGljdSwgdmVudCwgcG9wdWxhdGlvbiksDQogICAgLmZ1bnMgPSBzdW0sIG5hLnJtID0gVA0KICAgICkgJT4lIA0KICB1bmdyb3VwKCkgDQoNCiMgcmVtb3ZlIHRlbXAgZmlsZQ0KdW5saW5rKHRlbXApIA0KDQojIGdsaW1wc2Ugb2YgdGhlIGRhdGENCmdsaW1wc2Uoc3RfbG91aXNfYWdnX3RibCkNCmBgYA0KDQpGcm9tIHRoZSBwcmludG91dCBvZiB0aGUgZGF0YSwgd2UgaGF2ZSBgciBuY29sKHN0X2xvdWlzX2FnZ190YmwpYCB2YXJpYWJsZXMgYW5kICBgciBucm93KHN0X2xvdWlzX2FnZ190YmwpYCBkYXlzIG9mIGRhdGEuIE5vdGUgdGhhdCB0aGUgYGRvbWluYW50X3ZhcmlhbnRgIHdhcyBleWViYWxsZWQgYXMgbWVudGlvbmVkIHByZXZpb3VzbHkgYW5kIHRoZSBgaG9saWRheXNgIHZhcmlhYmxlIGFzc3VtZXMgdGhhdCB5b3VyIGhlYWx0aCBkZXBhcnRtZW50IG1hdGNoZXMgdGhlIGhvbGlkYXlzIHRha2VuIGJ5IHRoZSBOWVNFLg0KDQotLS0NCg0KIyBQbG90dGluZyB0aGUgRGF0YSBmb3IgU2FpbnQgTG91aXMNCg0KIyMgUmF3IERhdGEgZnJvbSB0aGUgUmVwbw0KDQpGcm9tIHRoZSBwbG90IGJlbG93LCBpdCBpcyBjbGVhciB0aGF0IHRoaXMgcHVibGljIGRhdGFzZXQgZG9lcyBub3QgY29udGFpbiBpbmZvcm1hdGlvbiBvbiBob3NwaXRhbGl6YXRpb25zLCB2ZW50LCBhbmQgSUNVLiBUaHVzLCBJIHdpbGwgbW9kZWwgZGVhdGhzIGFzIGEgZnVuY3Rpb24gb2YgY2FzZXMuICoqUGV0ZXIsIHlvdSBjYW4gY2hhbmdlIHRoZSB2YXJpYWJsZXMgYmFzZWQgb24geW91ciBkYXRhc2V0KiouDQoNCkl0IGlzIGFsc28gY2xlYXIgdGhhdCB0aGUgZGF0YSBpcyBjdW11bGF0aXZlIHNvIHdlIHdpbGwgZGlmZmVyZW5jZSB0byBoYXZlIHRoZSBkYWlseSBuZXcgY2FzZXMgYW5kIGRlYXRocy4NCg0KYGBge3IgZWRhLCBmaWcuaGVpZ2h0PTcuNX0NCnN0X2xvdWlzX2FnZ190YmwgJT4lIA0KICBwaXZvdF9sb25nZXIoY29scyA9IGMoY29uZmlybWVkLCBkZWF0aHMsIHJlY292ZXJlZCwgaG9zcCwgaWN1LCB2ZW50LCBwb3B1bGF0aW9uKSwNCiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gJ3N0YXRpc3RpYycpICU+JSANCiAgZ2dwbG90KA0KICAgIGFlcyh4ID0gZGF0ZSwgeSA9IHZhbHVlKQ0KICApICsgDQogIGdlb21fbGluZSgpICsNCiAgZmFjZXRfd3JhcCh+IHN0YXRpc3RpYywgc2NhbGVzID0gJ2ZyZWVfeScsIG5jb2wgPSAxKSArIA0KICB0aGVtZV9idygpICsNCiAgc2NhbGVfeF9kYXRlKGJyZWFrcyA9IHNjYWxlczo6cHJldHR5X2JyZWFrcyhuID0gMTIpKSArDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmNvbW1hKSArDQogIGxhYnModGl0bGUgPSAnUGxvdHMgb2YgdGhlIFRpbWUtU2VyaWVzIG9mIFBvdGVudGlhbCBWYXJpYWJsZXMgZm9yIFN0LiBMb3VpcyBNU0EnLA0KICAgICAgIHN1YnRpdGxlID0gJ0hvc3AsIElDVSwgUmVjb3ZlcmVkIGFuZCBWZW50IGhhdmUgbm8gZGF0YScsDQogICAgICAgY2FwdGlvbiA9ICdCYXNlZCBvbiBkYXRhIGFnZ3JlZ2F0ZWQgZnJvbSB0aGUgQ09WSUQxOSBEYXRhSHViJywNCiAgICAgICB4ID0gJ0RhdGUnLA0KICAgICAgIHkgPSBOVUxMKS0+IHAxDQoNCmdncGxvdGx5KHAxKQ0KYGBgDQoNCg0KIyMgRGlmZmVyZW5jZWQgRGF0YSBTdGFydGluZyBmcm9tIEphbiAwNCwgMjAyMQ0KDQpCYXNlZCBvbiBvdXIgZXhwbGFuYXRvcnkgYW5hbHlzaXMgb2YgdGhlIGRhdGEsIHRoZXJlIHNlZW1lZCB0byBiZSBzb21lIGxhcmdlIG91dGxpZXJzIGluIHRoZSBiZWdpbm5pbmcgb2YgMjAyMS4gU28gSSBhbSBzdGFydGluZyB0aGUgYW5hbHlzaXMgZnJvbSB0aGUgZmlyc3QgTW9uZGF5IG9mIHRoZSB5ZWFyLiBJIGRpZmZlcmVuY2VkIHRvIGNvbXB1dGUgdGhlIHZhcmlhYmxlcyBvZiBpbnRlcmVzdC4gVGhlIGNvZGUgYmVsb3cgcGxvdHMgdGhlIHR3byB0aW1lLXNlcmllcyBvZiBpbnRlcmVzdCAoYGNvbmZpcm1lZCBjYXNlc2AgYW5kIGBjb25maXJtZWQgZGVhdGhzYCksIGNvbG9yIGNvZGVkIGJhc2VkIG9uIHRoZSBDT1ZJRC0xOSBkb21pbmFudCB2YXJpYW50LiANCg0KYGBge3IgZWRhX2RpZmZ9DQpzdF9sb3Vpc19hZ2dfdGJsID0gDQogIHN0X2xvdWlzX2FnZ190YmwgJT4lIA0KICAjIGtlZXBpbmcgb25seSByZWxldmFudCBjb2x1bW5zIGZvciBmb3JlY2FzdGluZw0KICBzZWxlY3QoZGF0ZSwgY29uZmlybWVkLCBkZWF0aHMpICU+JSANCiAgIyBjb252ZXJ0aW5nIHRoZSBjdW11bGF0aXZlIGNvdW50cyB0byBuZXcNCiAgbXV0YXRlKA0KICAgIGNvbmZpcm1lZCA9IGNvbmZpcm1lZCAtIGxhZyhjb25maXJtZWQpLA0KICAgIGRlYXRocyA9IGRlYXRocyAtIGxhZyhkZWF0aHMpIA0KICApICU+JSANCiAgZmlsdGVyKGRhdGUgPj0geW1kKCcyMDIxLTAxLTA0JykpICU+JSANCiAgbXV0YXRlKA0KICAgICMgY3JlYXRpbmcgYSBkYXRhIGZyYW1lIG9mIHZhcmlhbnRzIGJhc2VkIG9uIA0KICAgICMgaHR0cHM6Ly93d3cubnl0aW1lcy5jb20vaW50ZXJhY3RpdmUvMjAyMS9oZWFsdGgvY29yb25hdmlydXMtdmFyaWFudC10cmFja2VyLmh0bWwNCiAgICBkb21pbmFudF92YXJpYW50ID0gDQogICAgICBjYXNlX3doZW4oDQogICAgICAgIGRhdGUgPCB5bWQoJzIwMjEtMDMtMDEnKSB+ICdFcHNpbG9uJywNCiAgICAgICAgZGF0ZSA8IHltZCgnMjAyMS0wNi0xNScpIH4gJ0FscGhhJywNCiAgICAgICAgZGF0ZSA8IHltZCgnMjAyMS0xMi0xNScpIH4gJ0RlbHRhJywNCiAgICAgICAgZGF0ZSA+PSB5bWQoJzIwMjEtMTItMTUnKSB+ICdPbWljcm9uJw0KICAgICAgKSAlPiUgYXNfZmFjdG9yKCksDQogICAgIyBjcmVhdGluZyBhIGxpc3Qgb2Ygc3BlY2lhbCBob2xpZGF5cw0KICAgICAgICAgaG9saWRheXMgPSANCiAgICAgIGlmX2Vsc2UoZGF0ZSAlaW4lIEhPTElEQVlfU0VRVUVOQ0Uoc3RhcnRfZGF0ZSA9IG1pbihkYXRlKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbmRfZGF0ZSA9IG1heChkYXRlKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjYWxlbmRhciA9ICdOWVNFJyksDQogICAgICAgICAgICAgIHRydWUgPSAneWVzJywNCiAgICAgICAgICAgICAgZmFsc2UgPSAnbm8nKSAlPiUgYXNfZmFjdG9yKCkNCiAgICAgICAgICAgKSAlPiUgDQogIGRyb3BfbmEoKQ0KDQpzdF9sb3Vpc19hZ2dfdGJsICU+JSANCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBjKGNvbmZpcm1lZCwgZGVhdGhzKSwNCiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gJ3N0YXRpc3RpYycpICU+JSANCiAgZ2dwbG90KA0KICAgIGFlcyh4ID0gZGF0ZSwgeSA9IHZhbHVlLCBjb2xvciA9IGRvbWluYW50X3ZhcmlhbnQpDQogICkgKyANCiAgZ2VvbV9saW5lKCkgKw0KICBmYWNldF93cmFwKH4gc3RhdGlzdGljLCBzY2FsZXMgPSAnZnJlZV95JywgbmNvbCA9IDEpICsgDQogIHRoZW1lX2J3KCkgKw0KICBzY2FsZV94X2RhdGUoYnJlYWtzID0gc2NhbGVzOjpwcmV0dHlfYnJlYWtzKG4gPSAxMikpICsgDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmNvbW1hKSArDQogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gJ1BhaXJlZCcpICsNCiAgbGFicyh4ID0gJ0RhdGUnLA0KICAgICAgIHkgPSAnTmV3IERhaWx5IENvdW50cycsIA0KICAgICAgIHRpdGxlID0gJ1Bsb3RzIG9mIENvbmZpcm1lZCBDYXNlcyBhbmQgRGVhdGhzIGZvciBTdC4gTG91aXMgTVNBJykgLT4gcDINCg0KZ2dwbG90bHkocDIpDQoNCiAgDQpgYGANCg0KQW4gaW1wb3J0YW50IHF1ZXN0aW9uIHRvIGFkZHJlc3MgKGlmIHRoaXMgaXMgaW4gUGV0ZXIncyBkYXRhKSBpcyBob3cgZG8gd2UgKipoYW5kbGUgbmVnYXRpdmUgY291bnRzKiouIEEgbmFpdmUgZml4IGluIFIgd291bGQgYmUgdG8gdXNlIHRoZSBtYXggb2YgdGhlIHZhbHVlIGFuZCAwIChzZWUgdGhlIGBwbWF4KClgKS4gSG93ZXZlciwgdGhpcyBpZ25vcmVzIHRoZSBtaXNyZXBvcnRpbmcgb2YgdGhlIGRhdGEuIA0KDQoNCiMjIEV4YW1pbmluZyB0aGUgQUNGLCBQQUNGIGFuZCBDQ0YgDQoNCiMjIyBBQ0Ygb2YgTmV3IERlYXRocw0KDQpgYGB7ciBhY2ZfZGVhdGhzfQ0KIyBleHRyYWN0IHRoZSBzdGFydGluZyB3ZWVrIG51bWJlciBhbmQgd2Vlay1kYXkgKGkuZS4sIDEgLTcgd2hlcmUgNyBpcyBTdW5kYXkpIG51bWJlcg0Kc3RhcnRpbmdfZGF5ID0gbWluKHN0X2xvdWlzX2FnZ190YmwkZGF0ZSkgJT4lIHdkYXkoKQ0Kc3RhcnRpbmdfd2VlayA9IG1pbihzdF9sb3Vpc19hZ2dfdGJsJGRhdGUpICU+JSB3ZWVrKCkNCg0KIyBjcmVhdGluZyBhIHRpbWVzZXJpZXMgZm9yIHRoZSBkZWF0aHMgKG91ciByZXNwb25zZSBvZiBpbnRlcmVzdCkNCmRlYXRoc190cyA9IHN0X2xvdWlzX2FnZ190YmwgJT4lIA0KICBwdWxsKGRlYXRocykgJT4lIA0KICB0cyggDQogICAgc3RhcnQgPSBjKHN0YXJ0aW5nX3dlZWssIHN0YXJ0aW5nX2RheSksDQogICAgZnJlcXVlbmN5ID0gNw0KICApDQoNCiMgcGxvdHRpbmcgdGhlIGFjZiBhcyBhIGdncGxvdCBvYmplY3Qgd2hpbGUgZHJvcHBpbmcgdGhlIHVzZWxlc3MgbGFnIDANCmFjZihkZWF0aHNfdHMsIGxhZy5tYXggPSA0MiwgcGxvdCA9IEYpICU+JSANCiAgYXV0b3Bsb3QoKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzY2FsZXM6OnByZXR0eV9icmVha3Mobj03KSApICsNCiAgdGhlbWVfYncoKSArDQogIGxhYnMoeCA9ICdMYWdzIGluIFdlZWtzJywNCiAgICAgICB0aXRsZSA9ICdBQ0Ygb2YgTmV3IERlYXRocyBmb3IgdGhlIEdyZWF0ZXIgU3QuIExvdWlzIE1TQScsDQogICAgICAgc3VidGl0bGUgPSAnVGhlIEFDRiBzaG93cyBhICJ3ZWVrbHkiIHBhdHRlcm4sIGkuZS4sIHdlIHdpbGwgbmVlZCBhIHNlYXNvbmFsIEFSSU1BIG1vZGVsJykNCiAgDQpgYGANCg0KDQojIyMgUEFDRiBvZiBOZXcgRGVhdGhzDQoNCmBgYHtyIHBhY2ZfZGVhdGhzfQ0KIyBwbG90dGluZyB0aGUgYWNmIGFzIGEgZ2dwbG90IG9iamVjdCB3aGlsZSBkcm9wcGluZyB0aGUgdXNlbGVzcyBsYWcgMA0KcGFjZihkZWF0aHNfdHMsIGxhZy5tYXggPSA0MiwgcGxvdCA9IEYpICU+JSANCiAgYXV0b3Bsb3QoKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzY2FsZXM6OnByZXR0eV9icmVha3Mobj03KSApICsNCiAgdGhlbWVfYncoKSArDQogIGxhYnMoeCA9ICdMYWdzIGluIFdlZWtzJywNCiAgICAgICB0aXRsZSA9ICdQQUNGIG9mIE5ldyBEZWF0aHMgZm9yIHRoZSBHcmVhdGVyIFN0LiBMb3VpcyBNU0EnLA0KICAgICAgIHN1YnRpdGxlID0gJ1RoZSBQQUNGIHNob3dzIGEgIndlZWtseSIgcGF0dGVybiwgaS5lLiwgd2Ugd2lsbCBuZWVkIGEgc2Vhc29uYWwgQVJJTUEgbW9kZWwnKQ0KICANCmBgYA0KDQoNCiMjIyBDQ0Ygb2YgTmV3IERlYXRocyBhbmQgQ29uZmlybWVkDQoNClRoZSBDQ0YgcGxvdCBiZWxvdyBzaG93cyBhIHNpZ25pZmljYW50IGNyb3NzIGNvcnJlbGF0aW9uIGJldHdlZW4gYm90aCBkZWF0aHMgYW5kIGNvbmZpcm1lZCBjYXNlcyBmb3IgdXAgdG8gNiB3ZWVrcyBvZiBkYXRhLiBOb3RlIHRoYXQgdGhlIHJlbGF0aW9uc2hpcCBzZWVtcyB0byBiZSBzaWduaWZpY2FudCBwcmltYXJpbHkgaW4gdGhlIG5lZ2F0aXZlIGRpcmVjdGlvbiwgd2hpY2ggKiptYWtlcyBzZW5zZSB0byBtZSoqIHNpbmNlIGxhZ2dlZCBjYXNlcyBhcmUgcHJlZGljdG9ycyBvZiBkZWF0aHMgYnV0IHRoZSBvdGhlciB3YXkgYXJvdW5kIGlzIG5vdCB0cnVlIHBhc3QgdHdvIHdlZWtzLiBUaGUgZGF0YSBiZWxvdyBhbHNvIHNob3dzICd3ZWVrbHkgc3Bpa2VzJy4NCg0KYGBge3IgY2NmfQ0KIyBjcmVhdGluZyBhIHRpbWVzZXJpZXMgZm9yIHRoZSBjb25maXJtZWQgY2FzZXMgKHNpbWlsYXIgdG8gYWJvdmUpDQpjb25maXJtZWRfdHMgPSBzdF9sb3Vpc19hZ2dfdGJsICU+JSANCiAgcHVsbChjb25maXJtZWQpICU+JSANCiAgdHMoIA0KICAgIHN0YXJ0ID0gYyhzdGFydGluZ193ZWVrLCBzdGFydGluZ19kYXkpLA0KICAgIGZyZXF1ZW5jeSA9IDcNCiAgKQ0KDQojIHBsb3R0aW5nIHRoZSBDQ0YgYXMgYSBnZ3Bsb3Qgb2JqZWN0DQpjY2YoeCA9IGNvbmZpcm1lZF90cywgeSA9IGRlYXRoc190cywgcGxvdCA9IEYsIGxhZy5tYXggPSA0MikgJT4lIA0KICAjIGNvbnZlcnRpbmcgdGhlIGNjZiBwbG90IHRvIGEgZ2dwbG90IG9iamVjdA0KICBhdXRvcGxvdCgpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNjYWxlczo6cHJldHR5X2JyZWFrcyhuPTEwKSkgKyANCiAgbGFicyh4ID0gJ0xhZyBpbiBXZWVrcycsIHkgPSAnQ0NGJywgDQogICAgICAgdGl0bGUgPSAnQ3Jvc3MgQ29ycmVsYXRpb24gRnVuY3Rpb24gb2YgRGVhdGhzIHZzLiBDb25maXJtZWQgQ2FzZXMnLA0KICAgICAgIHN1YnRpdGxlID0gJ0Nhc2VzIHNlZW0gdG8gYmUgcHJlZGljdG9ycyBvZiBkZWF0aHMgKG1vcmUgdGhhbiB0aGUgb3RoZXIgd2F5IGFyb3VuZCknKSArDQogIHRoZW1lX2J3KCkNCmBgYA0KDQoNCi0tLQ0KDQojIENyZWF0aW5nIHRpbWUgc3BsaXRzIGZvciBUcmFpbmluZyBhbmQgVmFsaWRhdGlvbg0KDQpCZWxvdywgSSBoYXZlIGNhcGl0YWxpemVkIG9uIHRoZSBgaW5pdGlhbF90aW1lX3NwbGl0KClgIHRvIHNwbGl0IHRoZSB0aW1lLXNlcmllcyBzdWNoIHRoYXQgdGhlIGZpcnN0IDkwJSBvZiB0aGUgZGF0YSBhcmUgdXNlZCBmb3IgbW9kZWwgYnVpbGRpbmcgYW5kIHRoZSByZW1haW5pbmcgMTAlIGFyZSB1c2VkIGZvciB2YWxpZGF0aW9uLiAqKldlIGNhbiByZWRvIHRoaXMgaWYgd2Ugc29sZWx5IHdhbnQgdG8gZm9jdXMgb24gdGhlIGV4cGxhbmF0b3J5IG1vZGVsaW5nIGNvbXBvbmVudC4qKg0KDQpgYGB7ciB0c19zcGxpdH0NCnNwbGl0cyA9IGluaXRpYWxfdGltZV9zcGxpdChzdF9sb3Vpc19hZ2dfdGJsLCBwcm9wID0gMC45KQ0KDQojIFByaW50aW5nIG91dCB0aGUgc3BsaXRzIHNvIHRoYXQgd2Uga25vdyB0aGUgbnVtYmVyIG9mIG9ic2VydmF0aW9ucyB1c2VkIGZvciBtb2RlbCB0cmFpbmluZw0Kc3BsaXRzDQoNCiMgVHJhaW5pbmcgZGF0YSBzdGFydCBhbmQgZW5kIGRhdGVzDQpwYXN0ZSgnVGhlIHN0YXJ0aW5nIGFuZCBlbmRpbmcgZGF0ZXMgZm9yIHRyYWluaW5nIGFyZScsDQogICAgICBzcGxpdHMkZGF0YVtzcGxpdHMkaW5faWQsICdkYXRlJ10gJT4lIGhlYWQobj0xKSAlPiUgcHVsbCgpLA0KICAgICAgJ2FuZCcsIA0KICAgICAgc3BsaXRzJGRhdGFbc3BsaXRzJGluX2lkLCAnZGF0ZSddICU+JSB0YWlsKG49MSkgJT4lIHB1bGwoKSwNCiAgICAgICdyZXNwZWN0aXZlbHkuIEZvciB0aGUgaG9sZG91dCBkYXRhLCB0aGUgc3RhcnRpbmcgYW5kIHRyYWluaWcgZGF0ZXMgYXJlJywNCiAgICAgIHNwbGl0cyRkYXRhW3NwbGl0cyRvdXRfaWQsICdkYXRlJ10gJT4lIGhlYWQobj0xKSAlPiUgcHVsbCgpLA0KICAgICAgJ2FuZCcsIA0KICAgICAgc3BsaXRzJGRhdGFbc3BsaXRzJG91dF9pZCwgJ2RhdGUnXSAlPiUgdGFpbChuPTEpICU+JSBwdWxsKCkpDQpgYGANCg0KLS0tDQoNCiMgVHJhaW5pbmcgRGlmZmVyZW50IFRpbWUtU2VyaWVzIE1vZGVscw0KDQpJbiB0aGlzIHNlY3Rpb24sIEkgaGF2ZSBxdWlja2x5IHRyYWluZWQgdGhlIGZvbGxvd2luZyBmaXZlIG1vZGVsczogIA0KDQotIEEgdW5pdmFyaWF0ZSBBdXRvIEFSSU1BIG1vZGVsIHdpdGggbm8geHJlZyAgDQotIEFuIEF1dG8gQVJJTUEgbW9kZWwgd2l0aCBjb25maXJtZWQsIGhvbGlkYXlzIChOWVNFIGhvbGlkYXlzKSBhbmQgZG9taW5hbnQgdmFyaWFudCBhcyBvdXIgeHJlZyAgDQotIEFuIEF1dG8gQXJpbWEgTW9kZWwgd2l0aCB4Z2Jvb3N0ICh1c2luZyB0aGUgeHJlZ3MpIG9uIHRoZSBBcmltYSBlcnJvcnMgIA0KLSBUaGUgUHJvcGhldCBNb2RlbCwgb3JpZ2luYWxseSBkZXZlbG9wZWQgYnkgW0ZhY2Vib29rXShodHRwczovL2ZhY2Vib29rLmdpdGh1Yi5pby9wcm9waGV0LykuIFNlZSB0aGUgW0ZvcmVjYXN0aW5nIGF0IFNjYWxlIFBhcGVyXShodHRwczovL3BlZXJqLmNvbS9wcmVwcmludHMvMzE5MC8pIGZvciBtb3JlIGRldGFpbHMuICANCi0gQSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCB3aXRoIHByZWRpY3RvcnMgYGFzLm51bWVyaWMoZGF0ZSkgKyBjb25maXJtZWQgKyBob2xpZGF5cyArIGRvbWluYW50X3ZhcmlhbnRgDQoNCkkgaGF2ZSBjYXBpdGFsaXplZCBvbiB0aGUgW21vZGVsdGltZSBwYWNrYWdlIHZpZ25ldHRlXShodHRwczovL2J1c2luZXNzLXNjaWVuY2UuZ2l0aHViLmlvL21vZGVsdGltZS9hcnRpY2xlcy9nZXR0aW5nLXN0YXJ0ZWQtd2l0aC1tb2RlbHRpbWUuaHRtbCkgdG8gcXVpY2tseSBydW4gdGhlc2UgbW9kZWxzLiBOb3RlIHRoYXQgdGhlIGBtb2RlbHRpbWUgQVBJYCBhbHNvIGFsbG93cyBmb3IgcnVubmluZyBvdGhlciBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscyBmb3IgdGltZS1zZXJpZXMgYXBwbGljYXRpb25zLg0KDQojIyBNb2RlbCBCdWlsZGluZw0KDQpgYGB7ciBtb2RlbF9idWlsZGluZywgbWVzc2FnZT1UUlVFfQ0KIyBhIGJhc2ljIHVuaXZhcmlhdGUgQVJJTUEgbW9kZWwgdXNpbmcg4oCcQXV0byBBcmltYeKAnSB1c2luZyBhcmltYV9yZWcoKQ0KIyB1c2luZyB0aGUgbW9kZWx0aW1lIHBrZyB0aGlzIHdpbGwgYXV0b21hdGljYWxseSBwaWNrIHRoZSB3ZWVrbHkgc2Vhc29uYWxpdHkNCm1vZGVsX2ZpdF9hcmltYSA9YXJpbWFfcmVnKCkgJT4lDQogICAgc2V0X2VuZ2luZShlbmdpbmUgPSAiYXV0b19hcmltYSIpICU+JQ0KICAgIGZpdChkZWF0aHMgfiBkYXRlLCBkYXRhID0gdHJhaW5pbmcoc3BsaXRzKSApDQoNCiMgQVJJTUEgd2l0aCB4cmVnDQptb2RlbF9maXRfYXJpbWFfeHJlZyA9IGFyaW1hX3JlZygpICU+JQ0KICBzZXRfZW5naW5lKGVuZ2luZSA9ICJhdXRvX2FyaW1hIikgJT4lDQogICMgY29uZmlybWVkLCBob2xpZGF5cyBhbmQgZG9taW5hbnQgdmFyaWFudCBhcyBvdXIgeHJlZw0KICBmaXQoDQogICAgZGVhdGhzIH4gZGF0ZSArIGNvbmZpcm1lZCArIGhvbGlkYXlzICsgZG9taW5hbnRfdmFyaWFudCwNCiAgICAgIGRhdGEgPSB0cmFpbmluZyhzcGxpdHMpIA0KICAgICkNCg0KIyBBdXRvIEFyaW1hIE1vZGVsIHdpdGggeGdib29zdCBvbiB0aGUgQXJpbWEgZXJyb3JzDQptb2RlbF9maXRfYXJpbWFfYm9vc3RlZCA9IGFyaW1hX2Jvb3N0KA0KICAgIG1pbl9uID0gMiwNCiAgICBsZWFybl9yYXRlID0gMC4wMTUNCikgJT4lDQogICAgc2V0X2VuZ2luZShlbmdpbmUgPSAiYXV0b19hcmltYV94Z2Jvb3N0IikgJT4lDQogICAgZml0KGRlYXRocyB+IGRhdGUgKyBjb25maXJtZWQgKyBob2xpZGF5cyArIGRvbWluYW50X3ZhcmlhbnQsDQogICAgICAgIGRhdGEgPSB0cmFpbmluZyhzcGxpdHMpICkNCg0KIyB0aGUgcHJvcGhldCBtb2RlbCB1c2VkIGJ5IEZhY2Vib29rDQptb2RlbF9maXRfcHJvcGhldCA9IHByb3BoZXRfcmVnKCkgJT4lDQogIHNldF9lbmdpbmUoZW5naW5lID0gInByb3BoZXQiKSAlPiUNCiAgZml0KGRlYXRocyB+IGRhdGUgKyBjb25maXJtZWQgKyBob2xpZGF5cyArIGRvbWluYW50X3ZhcmlhbnQsDQogICAgICBkYXRhID0gdHJhaW5pbmcoc3BsaXRzKSApDQoNCiMgTGluZWFyIFJlZ3Jlc3Npb24gDQptb2RlbF9maXRfbG0gPSBsaW5lYXJfcmVnKCkgJT4lDQogICAgc2V0X2VuZ2luZSgibG0iKSAlPiUNCiAgICBmaXQoZGVhdGhzIH4gYXMubnVtZXJpYyhkYXRlKSArIGNvbmZpcm1lZCArIGhvbGlkYXlzICsgZG9taW5hbnRfdmFyaWFudCwNCiAgICAgICAgZGF0YSA9IHRyYWluaW5nKHNwbGl0cykgKQ0KDQptb2RlbHNfdGJsID0gbW9kZWx0aW1lX3RhYmxlKA0KICAgIG1vZGVsX2ZpdF9hcmltYSwNCiAgICBtb2RlbF9maXRfYXJpbWFfeHJlZywNCiAgICBtb2RlbF9maXRfYXJpbWFfYm9vc3RlZCwNCiAgICBtb2RlbF9maXRfcHJvcGhldCwNCiAgICBtb2RlbF9maXRfbG0NCikNCg0KbW9kZWxzX3RibA0KYGBgDQoNCioqSWYgdGhpcyBkYXRhIGlzIGNsb3NlIHRvIHlvdXIgZGF0YSwgeW91IGNhbiBzZWUgdGhhdCB0aGUgZmlyc3Qgbm9uLXNlYXNvbmFsIGRpZmZlcmVuY2Ugd2FzIHRha2VuIGluIHRoZSBmaXJzdCBhbmQgdGhpcmQgYXV0by5hcmltYSgpIG1vZGVscy4qKiBUaGlzIGFuc3dlcnMgdGhlIHF1ZXN0aW9uIGZyb20gQWxsaXNvbiB0b2RheS4NCg0KDQojIyBUcmFpbmluZyBQZXJmb3JtYW5jZQ0KDQojIyMgUGxvdHRpbmcgdGhlIFJlc2lkdWFscw0KDQpUaGUgY29kZSBiZWxvdyBpcyB1c2VkIHRvIHBsb3QgdGhlIHJlc2lkdWFscyBmb3IgZWFjaCBvZiB0aGUgbW9kZWxzLiBUaGUgZmlyc3QgdGhyZWUgbGluZXMgb2YgY29kZSBhcmUgdmVyeSBzaW1pbGFyIHRvIHRoZSBjb2RlIHNob3duIGluIHRoZSBwYWNrYWdlIHZpZ25ldHRlLiBUaGUgYWRkaXRpb25hbCBjb2RlIGlzIHRvIGFsbG93IHlvdSB0byBjdXN0b21pemUgdGhlIHBsb3QgdG8geW91ciBsaWtpbmcuIA0KDQpgYGB7ciByZXNfdHJhaW5pbmdfdml6LCBmaWcuaGVpZ2h0PTcuNX0NCm1vZGVsc190YmwgJT4lDQogICAgbW9kZWx0aW1lX2NhbGlicmF0ZShuZXdfZGF0YSA9IHRyYWluaW5nKHNwbGl0cykpICU+JQ0KICAgIG1vZGVsdGltZV9yZXNpZHVhbHMoKSAlPiUNCiAgICBwbG90X21vZGVsdGltZV9yZXNpZHVhbHMoLmludGVyYWN0aXZlID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIC50eXBlID0gJ3RpbWVwbG90JykgKw0KICBzY2FsZV94X2RhdGUoYnJlYWtzID0gc2NhbGVzOjpwcmV0dHlfYnJlYWtzKDEyKSkgKw0KICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICdEYXJrMicpICsNCiAgZmFjZXRfd3JhcCh+IC5tb2RlbF9kZXNjLCBuY29sID0gMSwgc2NhbGVzID0gJ2ZyZWUnKSArDQogIHRoZW1lX2J3KCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnbm9uZScpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICdSZXNpZHVhbHMgcGxvdCBmb3IgdGhlIGZpdmUgbW9kZWxzIGJhc2VkIG9uIG91ciB0cmFpbmluZyBkYXRhJywNCiAgICBzdWJ0aXRsZSA9ICdUaGUgcmVzaWR1YWxzIGFyZSBsYXJnZSBvbiB0aGUgc2FtZSBkYXkgaXJyZXNwZWN0aXZlIG9mIG1vZGVsJykNCg0KYGBgDQoNCiMjIyBTdGF0aXN0aWNhbCBUZXN0cyBmb3IgdGhlIFJlc2lkdWFscw0KDQpUaGUgYG1vZGVsdGltZV9yZXNpZHVhbHNfdGVzdCgpYCBjb21wdXRlcyB0aGUgcmVzdWx0cyBmcm9tIDQgZGlmZmVyZW50IHN0YXRpc3RpY2FsIHRlc3RzOiAgDQoNCi0gKipTaGFwaXJvLVdpbGsgVGVzdCoqIHRlc3RzIHRoZSBOb3JtYWxpdHkgb2YgdGhlIHJlc2lkdWFscy4gVGhlIE51bGwgSHlwb3RoZXNpcyBpcyB0aGF0IHRoZSByZXNpZHVhbHMgYXJlIG5vcm1hbGx5IGRpc3RyaWJ1dGVkLiBBIGxvdyAqcC0qdmFsdWUgYmVsb3cgYSBnaXZlbiBzaWduaWZpY2FuY2UgbGV2ZWwgaW5kaWNhdGVzIHRoZSB2YWx1ZXMgYXJlICoqTk9UIE5vcm1hbGx5IERpc3RyaWJ1dGVkKiouICANCg0KLSBCb3RoIHRoZSAqKkJveC1QaWVyY2UqKiBhbmQgKipManVuZy1Cb3gqKiBUZXN0cyBhcmUgdXNlZCB0byB0ZXN0IGZvciB0aGUgYWJzZW5jZSBvZiBhdXRvY29ycmVsYXRpb24gaW4gcmVzaWR1YWxzLiBBIGxvdyBwLXZhbHVlIGJlbG93IGEgZ2l2ZW4gc2lnbmlmaWNhbmNlIGxldmVsIGluZGljYXRlcyB0aGUgdmFsdWVzIGFyZSBhdXRvY29ycmVsYXRlZC4gIA0KDQoNCmBgYHtyIHJlc190cmFpbmluZ190ZXN0fQ0KbW9kZWxzX3RibCAlPiUNCiAgICBtb2RlbHRpbWVfY2FsaWJyYXRlKG5ld19kYXRhID0gdHJhaW5pbmcoc3BsaXRzKSkgJT4lDQogICAgbW9kZWx0aW1lX3Jlc2lkdWFscygpICU+JQ0KICBtb2RlbHRpbWVfcmVzaWR1YWxzX3Rlc3QoKQ0KDQojIElmIHlvdSB3YW50IHRvIGV4cGxvcmUgdGhlIHJlc2lkdWFscyBzZXBlcmF0ZWx5LCB0aGV5IGFyZSBzdG9yZWQgYXMgZm9sbG93cw0KdHJhaW5pbmdfcmVzaWR1YWxzID0gbW9kZWxzX3RibCAlPiUNCiAgICBtb2RlbHRpbWVfY2FsaWJyYXRlKG5ld19kYXRhID0gdHJhaW5pbmcoc3BsaXRzKSkgJT4lDQogICAgbW9kZWx0aW1lX3Jlc2lkdWFscygpIA0KYGBgDQoNClRoZSByZXN1bHRzIGFib3ZlIGluZGljYXRlIHRoYXQgdGhlIHJlc2lkdWFscyBhcmUgKipub3Qgbm9ybWFsbHkgZGlzdHJpYnV0ZWQqKi4gSG93ZXZlciwgdGhleSBhbHNvIGluZGljYXRlIHRoYXQgbm8gc2lnbmlmaWNhbnQgYXV0b2NvcnJlbGF0aW9uIGlzIGV4aGliaXRlZCBpbiB0aGUgcmVzaWR1YWxzIGFzIGluZGljYXRlZCBieSBib3RoIHRoZSAqKkJveC1QaWVyY2UqKiBhbmQgKipManVuZy1Cb3gqKiB0ZXN0IHJlc3VsdHMuIA0KDQojIyMgTW9kZWwncyBUcmFpbmluZyBQZXJmb3JtYW5jZQ0KDQpNdWx0aXBsZSBhY2N1cmFjeSBtZWFzdXJlcyBjYW4gYmUgY29tcHV0ZWQgYXMgZm9sbG93cy4gDQpgYGB7ciByZXNfdHJhaW5pbmdfYWNjfQ0KbW9kZWxzX3RibCAlPiUNCiAgICBtb2RlbHRpbWVfY2FsaWJyYXRlKG5ld19kYXRhID0gdHJhaW5pbmcoc3BsaXRzKSkgJT4lDQogICAgbW9kZWx0aW1lX2FjY3VyYWN5KCkgJT4lDQogICAgdGFibGVfbW9kZWx0aW1lX2FjY3VyYWN5KA0KICAgICkNCmBgYA0KDQpOb3RlIHRoYXQgTUFQRSBpcyBpbmZpbml0ZSBpbiB0aGUgYWJvdmUgdGFibGUgc2luY2UgdGhlcmUgYXJlIHNldmVyYWwgZGF5cyB3aGVyZSB0aGUgY291bnQgb2YgYG5ldyBkZWF0aHMgPSAwYC4gVGhlIE1BU0UgaW5kaWNhdGVzIHRoYXQgYWxsIG1vZGVscyBpbXByb3ZlIG92ZXIgdGhlIG5haXZlIGZvcmVjYXN0IGJ5IGFib3V0IDI1JS4gSG93ZXZlciwgdGhlIHJzcSBmb3IgYWxsIG1vZGVscyBpcyBsb3cuICoqTWF5IGJlIHdlIG5lZWQgdG8gYWRkIGFkZGl0aW9uYWwgcHJlZGljdG9ycyoqLiANCg0KDQotLS0NCg0KIyBUZXN0aW5nIFBlcmZvcm1hbmNlDQoNClRoZSBuYW1lIG9mIHRoZSBmdW5jdGlvbiBmcm9tIHRoZSBgbW9kZWx0aW1lYCBwYWNrYWdlIGlzIHNvbWV3aGF0IGNvbmZ1c2luZy4gSXQgaXMgY2FsbGVkIGBjYWxpYnJhdGVgIGJ1dCBjYWxpYnJhdGluZyBhZGRzIGEgbmV3IGNvbHVtbiwgYC5jYWxpYnJhdGlvbl9kYXRhYCwgd2l0aCB0aGUgdGVzdCBwcmVkaWN0aW9ucyBhbmQgcmVzaWR1YWxzIGluc2lkZS4gQSBmZXcgbm90ZXMgb24gQ2FsaWJyYXRpb246ICANCg0KLSBDYWxpYnJhdGlvbiBpcyBob3cgY29uZmlkZW5jZSBpbnRlcnZhbHMgYW5kIGFjY3VyYWN5IG1ldHJpY3MgYXJlIGRldGVybWluZWQgIA0KLSBDYWxpYnJhdGlvbiBEYXRhIGlzIHNpbXBseSBmb3JlY2FzdGluZyBwcmVkaWN0aW9ucyBhbmQgcmVzaWR1YWxzIHRoYXQgYXJlIGNhbGN1bGF0ZWQgZnJvbSBvdXQtb2Ytc2FtcGxlIGRhdGEuICAgDQotIEFmdGVyIGNhbGlicmF0aW5nLCB0aGUgY2FsaWJyYXRpb24gZGF0YSBmb2xsb3dzIHRoZSBkYXRhIHRocm91Z2ggdGhlIGZvcmVjYXN0aW5nIHdvcmtmbG93Lg0KDQoNCmBgYHtyIHJlY2xhaWJyYXRpb259DQpjYWxpYnJhdGlvbl90YmwgPSBtb2RlbHNfdGJsICU+JQ0KICAgIG1vZGVsdGltZV9jYWxpYnJhdGUoIG5ld19kYXRhID0gdGVzdGluZyhzcGxpdHMpICkNCg0KY2FsaWJyYXRpb25fdGJsDQpgYGANCg0KUGVyIHRoZSBkZXNjcmlwdGlvbiBhYm92ZSB0aGUgY29kZSBjaHVuaywgdGhlIG1vZGVsIGRlc2NyaXB0aW9ucyBkaWQgbm90IGNoYW5nZSBhbmQgd2Ugb25seSBhcHBlbmRlZCB0aGUgdGVzdGluZyBkYXRhLg0KDQoNCiMjIEhvbGRvdXQgUGVyZm9ybWFuY2UNCg0KIyMjIFBsb3R0aW5nIHRoZSBSZXNpZHVhbHMNCg0KYGBge3IgcmVzX2hvbGRvdXRfdml6fQ0KbW9kZWxzX3RibCAlPiUNCiAgICBtb2RlbHRpbWVfY2FsaWJyYXRlKG5ld19kYXRhID0gdGVzdGluZyhzcGxpdHMpKSAlPiUNCiAgICBtb2RlbHRpbWVfcmVzaWR1YWxzKCkgJT4lDQogICAgcGxvdF9tb2RlbHRpbWVfcmVzaWR1YWxzKC5pbnRlcmFjdGl2ZSA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAudHlwZSA9ICd0aW1lcGxvdCcpICsNCiAgc2NhbGVfeF9kYXRlKGJyZWFrcyA9IHNjYWxlczo6cHJldHR5X2JyZWFrcygxMikpICsNCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAnRGFyazInKSArDQogIGZhY2V0X3dyYXAofiAubW9kZWxfZGVzYywgbmNvbCA9IDEpICsNCiAgdGhlbWVfYncoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJykgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gJ1Jlc2lkdWFscyBwbG90IGZvciB0aGUgZml2ZSBtb2RlbHMgYmFzZWQgb24gb3VyIGhvbGRvdXQgZGF0YScsDQogICAgc3VidGl0bGUgPSAnVGhlIEFSSU1BIHR5cGUgbW9kZWxzIGFyZSBtb3N0IGFjY3VyYXRlLiBMTSBpcyBvdmVyLXByZWRpY3Rpbmcgc2luY2UgcmVzaWR1YWxzIGFyZSBtb3N0bHkgbmVnYXRpdmUnKSANCg0KYGBgDQoNCiMjIyBTdGF0aXN0aWNhbCBUZXN0cyBmb3IgdGhlIFJlc2lkdWFscw0KDQpgYGB7ciByZXNfaG9sZG91dF90ZXN0fQ0KbW9kZWxzX3RibCAlPiUNCiAgICBtb2RlbHRpbWVfY2FsaWJyYXRlKG5ld19kYXRhID0gdGVzdGluZyhzcGxpdHMpKSAlPiUNCiAgICBtb2RlbHRpbWVfcmVzaWR1YWxzKCkgJT4lDQogIG1vZGVsdGltZV9yZXNpZHVhbHNfdGVzdCgpDQoNCmBgYA0KDQpUaGUgaG9sZG91dCByZXN1bHRzIGFyZSBjb25zaXN0ZW50IHdpdGggdGhlIG9ic2VydmF0aW9ucyBtYWRlIG9uIHRoZSB0cmFpbmluZyBkYXRhLiBPbmUgZXhjZXB0aW9uIGlzIHRoYXQgdGhlIEFSSU1BIHdpdGggeHJlZyAobW9kZWwgMikgc2hvd3MgY29ycmVsYXRlZCByZXNpZHVhbHMgb24gdGhlIGhvbGRvdXQgZGF0YXNldC4NCg0KDQojIyMgTW9kZWwncyBIb2xkb3V0IFBlcmZvcm1hbmNlDQoNCk5vdGUgdGhhdCBNQVBFIGlzIGluZmluaXRlIGluIHRoZSBiZWxvdyB0YWJsZSBzaW5jZSB0aGVyZSBhcmUgc2V2ZXJhbCBkYXlzIHdoZXJlIHRoZSBjb3VudCBvZiBgbmV3IGRlYXRocyA9IDBgDQpgYGB7ciByZXNfaG9sZG91dF9hY2N9DQptb2RlbHNfdGJsICU+JQ0KICAgIG1vZGVsdGltZV9jYWxpYnJhdGUobmV3X2RhdGEgPSB0ZXN0aW5nKHNwbGl0cykpICU+JQ0KICAgIG1vZGVsdGltZV9hY2N1cmFjeSgpICU+JQ0KICAgIHRhYmxlX21vZGVsdGltZV9hY2N1cmFjeSgNCiAgICApDQpgYGANCg0KVGhlIGZpcnN0IGFuZCB0aGlyZCBtb2RlbHMgY29udGludWUgdG8gYmUgdGhlIGJlc3QgbW9kZWxzLiBIb3dldmVyLCB0aGVpciBpbXByb3ZlbWVudCBvdmVyIHRoZSBuYWl2ZSBmb3JlY2FzdCAoaS5lLiwgcmFuZG9tIHdhbGspIGlzIHNtYWxsZXIgdGhhbiB0aGUgdHJhaW5pbmcgZGF0YXNldC4NCg0KDQo=